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;
21 private Photo _photo = null;
22 /** Attached audio clip */
23 private AudioClip _audio = null;
24 private String _waypointName = null;
25 private boolean _startOfSegment = false;
26 private boolean _markedForDeletion = false;
27 private int _modifyCount = 0;
31 * @param inValueArray array of String values
32 * @param inFieldList list of fields
33 * @param inAltFormat altitude format
35 public DataPoint(String[] inValueArray, FieldList inFieldList, Altitude.Format inAltFormat)
38 _fieldValues = inValueArray;
39 // save list of fields
40 _fieldList = inFieldList;
41 // Remove double quotes around values
42 removeQuotes(_fieldValues);
43 // parse fields into objects
44 parseFields(null, inAltFormat);
49 * Parse the string values into objects eg Coordinates
50 * @param inField field which has changed, or null for all
51 * @param inAltFormat altitude format
53 private void parseFields(Field inField, Altitude.Format inAltFormat)
55 if (inField == null || inField == Field.LATITUDE) {
56 _latitude = new Latitude(getFieldValue(Field.LATITUDE));
58 if (inField == null || inField == Field.LONGITUDE) {
59 _longitude = new Longitude(getFieldValue(Field.LONGITUDE));
61 if (inField == null || inField == Field.ALTITUDE) {
62 _altitude = new Altitude(getFieldValue(Field.ALTITUDE), inAltFormat);
64 if (inField == null || inField == Field.TIMESTAMP) {
65 _timestamp = new Timestamp(getFieldValue(Field.TIMESTAMP));
67 if (inField == null || inField == Field.WAYPT_NAME) {
68 _waypointName = getFieldValue(Field.WAYPT_NAME);
70 if (inField == null || inField == Field.NEW_SEGMENT)
72 String segmentStr = getFieldValue(Field.NEW_SEGMENT);
73 if (segmentStr != null) {segmentStr = segmentStr.trim();}
74 _startOfSegment = (segmentStr != null && (segmentStr.equals("1") || segmentStr.toUpperCase().equals("Y")));
80 * Constructor for additional points (eg interpolated, photos)
81 * @param inLatitude latitude
82 * @param inLongitude longitude
83 * @param inAltitude altitude
85 public DataPoint(Coordinate inLatitude, Coordinate inLongitude, Altitude inAltitude)
87 // Only these three fields are available
88 _fieldValues = new String[3];
89 Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE};
90 _fieldList = new FieldList(fields);
91 _latitude = inLatitude;
92 _fieldValues[0] = inLatitude.output(Coordinate.FORMAT_NONE);
93 _longitude = inLongitude;
94 _fieldValues[1] = inLongitude.output(Coordinate.FORMAT_NONE);
95 if (inAltitude == null) {
96 _altitude = Altitude.NONE;
99 _altitude = inAltitude;
100 _fieldValues[2] = "" + inAltitude.getValue();
102 _timestamp = new Timestamp(null);
107 * Get the value for the given field
108 * @param inField field to interrogate
109 * @return value of field
111 public String getFieldValue(Field inField)
113 return getFieldValue(_fieldList.getFieldIndex(inField));
118 * Get the value at the given index
119 * @param inIndex index number starting at zero
120 * @return field value, or null if not found
122 public String getFieldValue(int inIndex)
124 if (_fieldValues == null || inIndex < 0 || inIndex >= _fieldValues.length)
126 return _fieldValues[inIndex];
131 * Set (or edit) the specified field value
132 * @param inField Field to set
133 * @param inValue value to set
134 * @param inUndo true if undo operation, false otherwise
136 public void setFieldValue(Field inField, String inValue, boolean inUndo)
138 // See if this data point already has this field
139 int fieldIndex = _fieldList.getFieldIndex(inField);
140 // Add to field list if necessary
143 // If value is empty & field doesn't exist then do nothing
144 if (inValue == null || inValue.equals(""))
148 // value isn't empty so extend field list
149 fieldIndex = _fieldList.extendList(inField);
151 // Extend array of field values if necessary
152 if (fieldIndex >= _fieldValues.length)
154 resizeValueArray(fieldIndex);
156 // Set field value in array
157 _fieldValues[fieldIndex] = inValue;
158 // Increment edit count on all field edits except segment
159 if (inField != Field.NEW_SEGMENT) {
162 // Change Coordinate, Altitude, Name or Timestamp fields after edit
163 if (_altitude != null && _altitude.getFormat() != Altitude.Format.NO_FORMAT) {
164 // Altitude already present so reuse format
165 parseFields(inField, _altitude.getFormat());
168 // use default altitude format from config
169 parseFields(inField, Config.getUnitSet().getDefaultAltitudeFormat());
174 * Either increment or decrement the modify count, depending on whether it's an undo or not
175 * @param inUndo true for undo, false otherwise
177 public void setModified(boolean inUndo)
188 * @return field list for this point
190 public FieldList getFieldList()
195 /** @param inFlag true for start of track segment */
196 public void setSegmentStart(boolean inFlag)
198 setFieldValue(Field.NEW_SEGMENT, inFlag?"1":null, false);
202 * Mark the point for deletion
203 * @param inFlag true to delete, false to keep
205 public void setMarkedForDeletion(boolean inFlag) {
206 _markedForDeletion = inFlag;
209 /** @return latitude */
210 public Coordinate getLatitude()
214 /** @return longitude */
215 public Coordinate getLongitude()
219 /** @return true if point has altitude */
220 public boolean hasAltitude()
222 return _altitude.isValid();
224 /** @return altitude */
225 public Altitude getAltitude()
229 /** @return true if point has timestamp */
230 public boolean hasTimestamp()
232 return _timestamp.isValid();
234 /** @return timestamp */
235 public Timestamp getTimestamp()
239 /** @return waypoint name, if any */
240 public String getWaypointName()
242 return _waypointName;
245 /** @return true if start of new track segment */
246 public boolean getSegmentStart()
248 return _startOfSegment;
251 /** @return true if point marked for deletion */
252 public boolean getDeleteFlag()
254 return _markedForDeletion;
258 * @return true if point has a waypoint name
260 public boolean isWaypoint()
262 return (_waypointName != null && !_waypointName.equals(""));
266 * @return true if point has been modified since loading
268 public boolean isModified()
270 return _modifyCount > 0;
274 * Compare two DataPoint objects to see if they are duplicates
275 * @param inOther other object to compare
276 * @return true if the points are equivalent
278 public boolean isDuplicate(DataPoint inOther)
280 if (inOther == null) return false;
281 if (_longitude == null || _latitude == null
282 || inOther._longitude == null || inOther._latitude == null)
286 // Make sure photo points aren't specified as duplicates
287 if (_photo != null) return false;
288 // Compare latitude and longitude
289 if (!_longitude.equals(inOther._longitude) || !_latitude.equals(inOther._latitude))
293 // Note that conversion from decimal to dms can make non-identical points into duplicates
294 // Compare waypoint name (if any)
297 return !inOther.isWaypoint();
299 return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName));
304 * Set the photo for this data point
305 * @param inPhoto Photo object
307 public void setPhoto(Photo inPhoto) {
313 * @return associated Photo object
315 public Photo getPhoto() {
320 * Set the audio clip for this point
321 * @param inAudio audio object
323 public void setAudio(AudioClip inAudio) {
329 * @return associated audio object
331 public AudioClip getAudio() {
336 * Attach the given media object according to type
337 * @param inMedia either a photo or an audio clip
339 public void attachMedia(MediaObject inMedia)
341 if (inMedia != null) {
342 if (inMedia instanceof Photo) {
343 setPhoto((Photo) inMedia);
344 inMedia.setDataPoint(this);
346 else if (inMedia instanceof AudioClip) {
347 setAudio((AudioClip) inMedia);
348 inMedia.setDataPoint(this);
354 * @return true if the point is valid
356 public boolean isValid()
358 return _latitude.isValid() && _longitude.isValid();
362 * @return true if the point has either a photo or audio attached
364 public boolean hasMedia() {
365 return _photo != null || _audio != null;
369 * @return name of attached photo and/or audio
371 public String getMediaName()
373 String mediaName = null;
374 if (_photo != null) mediaName = _photo.getName();
377 if (mediaName == null) {
378 mediaName = _audio.getName();
381 mediaName = mediaName + ", " + _audio.getName();
388 * Interpolate a set of points between this one and the given one
389 * @param inEndPoint end point of interpolation
390 * @param inNumPoints number of points to generate
391 * @return the DataPoint array
393 public DataPoint[] interpolate(DataPoint inEndPoint, int inNumPoints)
395 DataPoint[] range = new DataPoint[inNumPoints];
397 for (int i=0; i<inNumPoints; i++)
399 Coordinate latitude = Coordinate.interpolate(_latitude, inEndPoint.getLatitude(), i, inNumPoints);
400 Coordinate longitude = Coordinate.interpolate(_longitude, inEndPoint.getLongitude(), i, inNumPoints);
401 Altitude altitude = Altitude.interpolate(_altitude, inEndPoint.getAltitude(), i, inNumPoints);
402 range[i] = new DataPoint(latitude, longitude, altitude);
408 * Interpolate between the two given points
409 * @param inStartPoint start point
410 * @param inEndPoint end point
411 * @param inFrac fractional distance from first point (0.0 to 1.0)
412 * @return new DataPoint object between two given ones
414 public static DataPoint interpolate(DataPoint inStartPoint, DataPoint inEndPoint, double inFrac)
416 if (inStartPoint == null || inEndPoint == null) {return null;}
417 return new DataPoint(
418 Coordinate.interpolate(inStartPoint.getLatitude(), inEndPoint.getLatitude(), inFrac),
419 Coordinate.interpolate(inStartPoint.getLongitude(), inEndPoint.getLongitude(), inFrac),
420 Altitude.interpolate(inStartPoint.getAltitude(), inEndPoint.getAltitude(), inFrac)
425 * Calculate the number of radians between two points (for distance calculation)
426 * @param inPoint1 first point
427 * @param inPoint2 second point
428 * @return angular distance between points in radians
430 public static double calculateRadiansBetween(DataPoint inPoint1, DataPoint inPoint2)
432 if (inPoint1 == null || inPoint2 == null)
434 final double TO_RADIANS = Math.PI / 180.0;
435 // Get lat and long from points
436 double lat1 = inPoint1.getLatitude().getDouble() * TO_RADIANS;
437 double lat2 = inPoint2.getLatitude().getDouble() * TO_RADIANS;
438 double lon1 = inPoint1.getLongitude().getDouble() * TO_RADIANS;
439 double lon2 = inPoint2.getLongitude().getDouble() * TO_RADIANS;
440 // Formula given by Wikipedia:Great-circle_distance as follows:
441 // angle = 2 arcsin( sqrt( (sin ((lat2-lat1)/2))^^2 + cos(lat1)cos(lat2)(sin((lon2-lon1)/2))^^2))
442 double firstSine = Math.sin((lat2-lat1) / 2.0);
443 double secondSine = Math.sin((lon2-lon1) / 2.0);
444 double term2 = Math.cos(lat1) * Math.cos(lat2) * secondSine * secondSine;
445 double answer = 2 * Math.asin(Math.sqrt(firstSine*firstSine + term2));
452 * Resize the value array
453 * @param inNewIndex new index to allow
455 private void resizeValueArray(int inNewIndex)
457 int newSize = inNewIndex + 1;
458 if (newSize > _fieldValues.length)
460 String[] newArray = new String[newSize];
461 System.arraycopy(_fieldValues, 0, newArray, 0, _fieldValues.length);
462 _fieldValues = newArray;
468 * @return a clone object with copied data
470 public DataPoint clonePoint()
472 // Copy all values (note that photo not copied)
473 String[] valuesCopy = new String[_fieldValues.length];
474 System.arraycopy(_fieldValues, 0, valuesCopy, 0, _fieldValues.length);
475 // Make new object to hold cloned data
476 DataPoint point = new DataPoint(valuesCopy, _fieldList, _altitude.getFormat());
482 * Remove all single and double quotes surrounding each value
483 * @param inValues array of values
485 private static void removeQuotes(String[] inValues)
487 if (inValues == null) {return;}
488 for (int i=0; i<inValues.length; i++)
490 inValues[i] = removeQuotes(inValues[i]);
495 * Remove any single or double quotes surrounding a value
496 * @param inValue value to modify
497 * @return modified String
499 private static String removeQuotes(String inValue)
501 if (inValue == null) {return inValue;}
502 final int len = inValue.length();
503 if (len <= 1) {return inValue;}
504 // get the first and last characters
505 final char firstChar = inValue.charAt(0);
506 final char lastChar = inValue.charAt(len-1);
507 if (firstChar == lastChar)
509 if (firstChar == '\"' || firstChar == '\'') {
510 return inValue.substring(1, len-1);
517 * Get string for debug
518 * @see java.lang.Object#toString()
520 public String toString()
522 return "[Lat=" + getLatitude().toString() + ", Lon=" + getLongitude().toString() + "]";