package tim.prune.data; import tim.prune.config.Config; /** * Class to represent a single data point in the series * including all its fields * Can be either a track point or a waypoint */ public class DataPoint { /** Array of Strings holding raw values */ private String[] _fieldValues = null; /** list of field definitions */ private FieldList _fieldList = null; /** Special fields for coordinates */ private Coordinate _latitude = null, _longitude = null; private Altitude _altitude = null; private Speed _hSpeed = null, _vSpeed = null; private Timestamp _timestamp = null; /** Attached photo */ private Photo _photo = null; /** Attached audio clip */ private AudioClip _audio = null; private String _waypointName = null; private boolean _startOfSegment = false; private boolean _markedForDeletion = false; private int _modifyCount = 0; /** * Constructor * @param inValueArray array of String values * @param inFieldList list of fields * @param inOptions creation options such as units */ public DataPoint(String[] inValueArray, FieldList inFieldList, PointCreateOptions inOptions) { // save data _fieldValues = inValueArray; // save list of fields _fieldList = inFieldList; // Remove double quotes around values removeQuotes(_fieldValues); // parse fields into objects parseFields(null, inOptions); } /** * Parse the string values into objects eg Coordinates * @param inField field which has changed, or null for all * @param inOptions creation options such as units */ private void parseFields(Field inField, PointCreateOptions inOptions) { if (inOptions == null) inOptions = new PointCreateOptions(); if (inField == null || inField == Field.LATITUDE) { _latitude = new Latitude(getFieldValue(Field.LATITUDE)); } if (inField == null || inField == Field.LONGITUDE) { _longitude = new Longitude(getFieldValue(Field.LONGITUDE)); } if (inField == null || inField == Field.ALTITUDE) { Unit altUnit = inOptions.getAltitudeUnits(); if (_altitude != null && _altitude.getUnit() != null) { altUnit = _altitude.getUnit(); } _altitude = new Altitude(getFieldValue(Field.ALTITUDE), altUnit); } if (inField == null || inField == Field.SPEED) { _hSpeed = new Speed(getFieldValue(Field.SPEED), inOptions.getSpeedUnits()); } if (inField == null || inField == Field.VERTICAL_SPEED) { _vSpeed = new Speed(getFieldValue(Field.VERTICAL_SPEED), inOptions.getVerticalSpeedUnits()); if (!inOptions.getVerticalSpeedsUpwards()) { _vSpeed.invert(); } } if (inField == null || inField == Field.TIMESTAMP) { _timestamp = new TimestampUtc(getFieldValue(Field.TIMESTAMP)); } if (inField == null || inField == Field.WAYPT_NAME) { _waypointName = getFieldValue(Field.WAYPT_NAME); } if (inField == null || inField == Field.NEW_SEGMENT) { String segmentStr = getFieldValue(Field.NEW_SEGMENT); if (segmentStr != null) {segmentStr = segmentStr.trim();} _startOfSegment = (segmentStr != null && (segmentStr.equals("1") || segmentStr.toUpperCase().equals("Y"))); } } /** * Constructor for additional points (eg interpolated, photos) * @param inLatitude latitude * @param inLongitude longitude * @param inAltitude altitude */ public DataPoint(Coordinate inLatitude, Coordinate inLongitude, Altitude inAltitude) { // Only these three fields are available _fieldValues = new String[3]; Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE}; _fieldList = new FieldList(fields); _latitude = inLatitude; _fieldValues[0] = inLatitude.output(Coordinate.FORMAT_NONE); _longitude = inLongitude; _fieldValues[1] = inLongitude.output(Coordinate.FORMAT_NONE); if (inAltitude == null) { _altitude = Altitude.NONE; } else { _altitude = inAltitude; _fieldValues[2] = "" + inAltitude.getValue(); } _timestamp = new TimestampUtc(null); } /** * Get the value for the given field * @param inField field to interrogate * @return value of field */ public String getFieldValue(Field inField) { return getFieldValue(_fieldList.getFieldIndex(inField)); } /** * Get the value at the given index * @param inIndex index number starting at zero * @return field value, or null if not found */ private String getFieldValue(int inIndex) { if (_fieldValues == null || inIndex < 0 || inIndex >= _fieldValues.length) return null; return _fieldValues[inIndex]; } /** * Set (or edit) the specified field value * @param inField Field to set * @param inValue value to set * @param inUndo true if undo operation, false otherwise */ public void setFieldValue(Field inField, String inValue, boolean inUndo) { // See if this data point already has this field int fieldIndex = _fieldList.getFieldIndex(inField); // Add to field list if necessary if (fieldIndex < 0) { // If value is empty & field doesn't exist then do nothing if (inValue == null || inValue.equals("")) { return; } // value isn't empty so extend field list fieldIndex = _fieldList.extendList(inField); } // Extend array of field values if necessary if (fieldIndex >= _fieldValues.length) { resizeValueArray(fieldIndex); } // Set field value in array _fieldValues[fieldIndex] = inValue; // Increment edit count on all field edits except segment if (inField != Field.NEW_SEGMENT) { setModified(inUndo); } // Change Coordinate, Altitude, Name or Timestamp fields after edit if (_altitude != null && _altitude.getUnit() != null) { // Altitude already present so reuse format parseFields(inField, null); // current units will be used } else { // use default altitude format from config parseFields(inField, Config.getUnitSet().getDefaultOptions()); } } /** * Either increment or decrement the modify count, depending on whether it's an undo or not * @param inUndo true for undo, false otherwise */ public void setModified(boolean inUndo) { if (!inUndo) { _modifyCount++; } else { _modifyCount--; } } /** * @return field list for this point */ public FieldList getFieldList() { return _fieldList; } /** @param inFlag true for start of track segment */ public void setSegmentStart(boolean inFlag) { setFieldValue(Field.NEW_SEGMENT, inFlag?"1":null, false); } /** * Mark the point for deletion * @param inFlag true to delete, false to keep */ public void setMarkedForDeletion(boolean inFlag) { _markedForDeletion = inFlag; } /** @return latitude */ public Coordinate getLatitude() { return _latitude; } /** @return longitude */ public Coordinate getLongitude() { return _longitude; } /** @return true if point has altitude */ public boolean hasAltitude() { return _altitude != null && _altitude.isValid(); } /** @return altitude */ public Altitude getAltitude() { return _altitude; } /** @return true if point has horizontal speed (loaded as field) */ public boolean hasHSpeed() { return _hSpeed != null && _hSpeed.isValid(); } /** @return horizontal speed */ public Speed getHSpeed() { return _hSpeed; } /** @return true if point has vertical speed (loaded as field) */ public boolean hasVSpeed() { return _vSpeed != null && _vSpeed.isValid(); } /** @return vertical speed */ public Speed getVSpeed() { return _vSpeed; } /** @return true if point has timestamp */ public boolean hasTimestamp() { return _timestamp.isValid(); } /** @return timestamp */ public Timestamp getTimestamp() { return _timestamp; } /** @return waypoint name, if any */ public String getWaypointName() { return _waypointName; } /** @return true if start of new track segment */ public boolean getSegmentStart() { return _startOfSegment; } /** @return true if point marked for deletion */ public boolean getDeleteFlag() { return _markedForDeletion; } /** * @return true if point has a waypoint name */ public boolean isWaypoint() { return (_waypointName != null && !_waypointName.equals("")); } /** * @return true if point has been modified since loading */ public boolean isModified() { return _modifyCount > 0; } /** * Compare two DataPoint objects to see if they are duplicates * @param inOther other object to compare * @return true if the points are equivalent */ public boolean isDuplicate(DataPoint inOther) { if (inOther == null) return false; if (_longitude == null || _latitude == null || inOther._longitude == null || inOther._latitude == null) { return false; } // Make sure photo points aren't specified as duplicates if (_photo != null) return false; // Compare latitude and longitude if (!_longitude.equals(inOther._longitude) || !_latitude.equals(inOther._latitude)) { return false; } // Note that conversion from decimal to dms can make non-identical points into duplicates // Compare waypoint name (if any) if (!isWaypoint()) { return !inOther.isWaypoint(); } return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName)); } /** * Add an altitude offset to this point, and keep the point's string value in sync * @param inOffset offset as double * @param inUnit unit of offset, feet or metres * @param inDecimals number of decimal places */ public void addAltitudeOffset(double inOffset, Unit inUnit, int inDecimals) { if (hasAltitude()) { _altitude.addOffset(inOffset, inUnit, inDecimals); _fieldValues[_fieldList.getFieldIndex(Field.ALTITUDE)] = _altitude.getStringValue(null); setModified(false); } } /** * Reset the altitude to the previous value (by an undo) * @param inClone altitude object cloned from earlier */ public void resetAltitude(Altitude inClone) { _altitude.reset(inClone); _fieldValues[_fieldList.getFieldIndex(Field.ALTITUDE)] = _altitude.getStringValue(null); setModified(true); } /** * Add a time offset to this point * @param inOffset offset to add (-ve to subtract) */ public void addTimeOffsetSeconds(long inOffset) { if (hasTimestamp()) { _timestamp.addOffsetSeconds(inOffset); _fieldValues[_fieldList.getFieldIndex(Field.TIMESTAMP)] = _timestamp.getText(null); setModified(false); } } /** * Set the photo for this data point * @param inPhoto Photo object */ public void setPhoto(Photo inPhoto) { _photo = inPhoto; _modifyCount++; } /** * @return associated Photo object */ public Photo getPhoto() { return _photo; } /** * Set the audio clip for this point * @param inAudio audio object */ public void setAudio(AudioClip inAudio) { _audio = inAudio; _modifyCount++; } /** * @return associated audio object */ public AudioClip getAudio() { return _audio; } /** * Attach the given media object according to type * @param inMedia either a photo or an audio clip */ public void attachMedia(MediaObject inMedia) { if (inMedia != null) { if (inMedia instanceof Photo) { setPhoto((Photo) inMedia); inMedia.setDataPoint(this); } else if (inMedia instanceof AudioClip) { setAudio((AudioClip) inMedia); inMedia.setDataPoint(this); } } } /** * @return true if the point is valid */ public boolean isValid() { return _latitude.isValid() && _longitude.isValid(); } /** * @return true if the point has either a photo or audio attached */ public boolean hasMedia() { return _photo != null || _audio != null; } /** * @return name of attached photo and/or audio */ public String getMediaName() { String mediaName = null; if (_photo != null) mediaName = _photo.getName(); if (_audio != null) { if (mediaName == null) { mediaName = _audio.getName(); } else { mediaName = mediaName + ", " + _audio.getName(); } } return mediaName; } /** * Interpolate a set of points between this one and the given one * @param inEndPoint end point of interpolation * @param inNumPoints number of points to generate * @return the DataPoint array */ public DataPoint[] interpolate(DataPoint inEndPoint, int inNumPoints) { DataPoint[] range = new DataPoint[inNumPoints]; // Loop over points for (int i=0; i _fieldValues.length) { String[] newArray = new String[newSize]; System.arraycopy(_fieldValues, 0, newArray, 0, _fieldValues.length); _fieldValues = newArray; } } /** * @return a clone object with copied data */ public DataPoint clonePoint() { // Copy all values (note that photo not copied) String[] valuesCopy = new String[_fieldValues.length]; System.arraycopy(_fieldValues, 0, valuesCopy, 0, _fieldValues.length); PointCreateOptions options = new PointCreateOptions(); if (_altitude != null) { options.setAltitudeUnits(_altitude.getUnit()); } // Make new object to hold cloned data DataPoint point = new DataPoint(valuesCopy, _fieldList, options); // Copy the speed information if (hasHSpeed()) { point.getHSpeed().copyFrom(_hSpeed); } if (hasVSpeed()) { point.getVSpeed().copyFrom(_vSpeed); } return point; } /** * Remove all single and double quotes surrounding each value * @param inValues array of values */ private static void removeQuotes(String[] inValues) { if (inValues == null) {return;} for (int i=0; i