X-Git-Url: http://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2Fdata%2FTrack.java;h=df51e8f27e3c8963189b664adefc7534861188ea;hb=54b9d8bc8f0025ccf97a67d9dd217ef1f9cf082f;hp=e73fa736eb6731d1d7c86ee5d48dffc6bf35b0eb;hpb=d3679d647d57c2ee7376ddbf6def2d5b23c04307;p=GpsPrune.git diff --git a/tim/prune/data/Track.java b/tim/prune/data/Track.java index e73fa73..df51e8f 100644 --- a/tim/prune/data/Track.java +++ b/tim/prune/data/Track.java @@ -2,9 +2,11 @@ package tim.prune.data; import java.util.List; +import tim.prune.Config; import tim.prune.UpdateMessageBroker; -import tim.prune.edit.FieldEdit; -import tim.prune.edit.FieldEditList; +import tim.prune.function.edit.FieldEdit; +import tim.prune.function.edit.FieldEditList; +import tim.prune.gui.map.MapUtils; /** @@ -13,8 +15,6 @@ import tim.prune.edit.FieldEditList; */ public class Track { - // Broker object - UpdateMessageBroker _broker = null; // Data points private DataPoint[] _dataPoints = null; // Scaled x, y values @@ -32,13 +32,10 @@ public class Track /** - * Constructor giving arrays of Fields and Objects - * @param inFieldArray field array - * @param inPointArray 2d array of field values + * Constructor for empty track */ - public Track(UpdateMessageBroker inBroker) + public Track() { - _broker = inBroker; // create field list _masterFieldList = new FieldList(null); // make empty DataPoint array @@ -55,8 +52,13 @@ public class Track * @param inPointArray 2d object array containing data * @param inAltFormat altitude format */ - public void load(Field[] inFieldArray, Object[][] inPointArray, int inAltFormat) + public void load(Field[] inFieldArray, Object[][] inPointArray, Altitude.Format inAltFormat) { + if (inFieldArray == null || inPointArray == null) + { + _numPoints = 0; + return; + } // copy field list _masterFieldList = new FieldList(inFieldArray); // make DataPoint object from each point in inPointList @@ -75,17 +77,35 @@ public class Track } } _numPoints = pointIndex; + // Set first track point to be start of segment + DataPoint firstTrackPoint = getNextTrackPoint(0); + if (firstTrackPoint != null) { + firstTrackPoint.setSegmentStart(true); + } // needs to be scaled _scaled = false; } + /** + * Load the track by transferring the contents from a loaded Track object + * @param inOther Track object containing loaded data + */ + public void load(Track inOther) + { + _numPoints = inOther._numPoints; + _masterFieldList = inOther._masterFieldList; + _dataPoints = inOther._dataPoints; + // needs to be scaled + _scaled = false; + } + ////////////////// Modification methods ////////////////////// /** * Combine this Track with new data - * @param inOtherTrack + * @param inOtherTrack other track to combine */ public void combine(Track inOtherTrack) { @@ -102,7 +122,7 @@ public class Track // needs to be scaled again _scaled = false; // inform listeners - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); } @@ -117,53 +137,27 @@ public class Track _numPoints = inNewSize; // needs to be scaled again _scaled = false; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); } } /** - * Compress the track to the given resolution - * @param inResolution resolution + * Delete the points marked for deletion * @return number of points deleted */ - public int compress(int inResolution) + public int deleteMarkedPoints() { - // (maybe should be separate thread?) - // (maybe should be in separate class?) - // (maybe should be based on subtended angles instead of distances?) - - if (inResolution <= 0) return 0; int numCopied = 0; - // Establish range of track and minimum range between points - scalePoints(); - double wholeScale = _xRange.getMaximum() - _xRange.getMinimum(); - double yscale = _yRange.getMaximum() - _yRange.getMinimum(); - if (yscale > wholeScale) wholeScale = yscale; - double minDist = wholeScale / inResolution; - // Copy selected points DataPoint[] newPointArray = new DataPoint[_numPoints]; - int[] pointIndices = new int[_numPoints]; for (int i=0; i<_numPoints; i++) { - boolean keepPoint = true; - if (!_dataPoints[i].isWaypoint()) - { - // go through newPointArray to check for range - for (int j=0; j 0) - { - // Make new resized array and copy DataPoints over - DataPoint[] newPointArray = new DataPoint[_numPoints - numDupes]; - j = 0; - for (i=0; i<_numPoints; i++) - { - if (!dupes[i]) - { - newPointArray[j] = _dataPoints[i]; - j++; - } - } - // Copy array references - _dataPoints = newPointArray; - _numPoints = _dataPoints.length; - _scaled = false; - _broker.informSubscribers(); - } - return numDupes; - } - - /** * Reverse the specified range of points + * @param inStart start index + * @param inEnd end index * @return true if successful, false otherwise */ public boolean reverseRange(int inStart, int inEnd) @@ -319,13 +255,74 @@ public class Track _dataPoints[inStart + i] = _dataPoints[inEnd - i]; _dataPoints[inEnd - i] = p; } + // adjust segment starts + shiftSegmentStarts(inStart, inEnd); + // Find first track point and following track point, and set segment starts to true + DataPoint firstTrackPoint = getNextTrackPoint(inStart); + if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);} + DataPoint nextTrackPoint = getNextTrackPoint(inEnd+1); + if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);} // needs to be scaled again _scaled = false; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); return true; } + /** + * Add the given time offset to the specified range + * @param inStart start of range + * @param inEnd end of range + * @param inOffset offset to add (-ve to subtract) + * @return true on success + */ + public boolean addTimeOffset(int inStart, int inEnd, long inOffset) + { + // sanity check + if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) { + return false; + } + boolean foundTimestamp = false; + // Loop over all points within range + for (int i=inStart; i<=inEnd; i++) + { + Timestamp timestamp = _dataPoints[i].getTimestamp(); + if (timestamp != null) + { + // This point has a timestamp so add the offset to it + foundTimestamp = true; + timestamp.addOffset(inOffset); + } + } + return foundTimestamp; + } + + + /** + * Merge the track segments within the given range + * @param inStart start index + * @param inEnd end index + * @return true if successful + */ + public boolean mergeTrackSegments(int inStart, int inEnd) + { + boolean firstTrackPoint = true; + // Loop between start and end + for (int i=inStart; i<=inEnd; i++) { + DataPoint point = getPoint(i); + // Set all segments to false apart from first track point + if (point != null && !point.isWaypoint()) { + point.setSegmentStart(firstTrackPoint); + firstTrackPoint = false; + } + } + // Find following track point, if any + DataPoint nextPoint = getNextTrackPoint(inEnd+1); + if (nextPoint != null) {nextPoint.setSegmentStart(true);} + UpdateMessageBroker.informSubscribers(); + return true; + } + /** * Collect all waypoints to the start or end of the track * @param inAtStart true to collect at start, false for end @@ -376,7 +373,7 @@ public class Track } // needs to be scaled again _scaled = false; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); return true; } @@ -434,11 +431,65 @@ public class Track _dataPoints = dataCopy; // needs to be scaled again to recalc x, y _scaled = false; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); return true; } - // TODO: Need to rearrange photo points too? + + /** + * Cut and move the specified section + * @param inSectionStart start index of section + * @param inSectionEnd end index of section + * @param inMoveTo index of move to point + * @return true if move successful + */ + public boolean cutAndMoveSection(int inSectionStart, int inSectionEnd, int inMoveTo) + { + // Check that indices make sense + if (inSectionStart > 0 && inSectionEnd > inSectionStart && inMoveTo > 0 + && (inMoveTo < inSectionStart || inMoveTo > (inSectionEnd+1))) + { + // do the cut and move + DataPoint[] newPointArray = new DataPoint[_numPoints]; + // System.out.println("Cut/move section (" + inSectionStart + " - " + inSectionEnd + ") to before point " + inMoveTo); + // Is it a forward copy or a backward copy? + if (inSectionStart > inMoveTo) + { + int sectionLength = inSectionEnd - inSectionStart + 1; + // move section to earlier point + if (inMoveTo > 0) + System.arraycopy(_dataPoints, 0, newPointArray, 0, inMoveTo); // unchanged points before + System.arraycopy(_dataPoints, inSectionStart, newPointArray, inMoveTo, sectionLength); // moved bit + // after insertion point, before moved bit + if (inSectionStart > (inMoveTo + 1)) + System.arraycopy(_dataPoints, inMoveTo, newPointArray, inMoveTo + sectionLength, inSectionStart - inMoveTo); + // after moved bit + if (inSectionEnd < (_numPoints - 1)) + System.arraycopy(_dataPoints, inSectionEnd+1, newPointArray, inSectionEnd+1, _numPoints - inSectionEnd - 1); + } + else + { + // Move section to later point + if (inSectionStart > 0) + System.arraycopy(_dataPoints, 0, newPointArray, 0, inSectionStart); // unchanged points before + // from end of section to move to point + if (inMoveTo > (inSectionEnd + 1)) + System.arraycopy(_dataPoints, inSectionEnd+1, newPointArray, inSectionStart, inMoveTo - inSectionEnd - 1); + // moved bit + System.arraycopy(_dataPoints, inSectionStart, newPointArray, inSectionStart + inMoveTo - inSectionEnd - 1, + inSectionEnd - inSectionStart + 1); + // unchanged bit after + if (inSectionEnd < (_numPoints - 1)) + System.arraycopy(_dataPoints, inMoveTo, newPointArray, inMoveTo, _numPoints - inMoveTo); + } + // Copy array references + _dataPoints = newPointArray; + _scaled = false; + return true; + } + return false; + } + /** * Interpolate extra points between two selected ones @@ -464,6 +515,53 @@ public class Track } + /** + * Average selected points + * @param inStartIndex start index of selection + * @param inEndIndex end index of selection + * @return true if successful + */ + public boolean average(int inStartIndex, int inEndIndex) + { + // check parameters + if (inStartIndex < 0 || inStartIndex >= _numPoints || inEndIndex <= inStartIndex) + return false; + + DataPoint startPoint = getPoint(inStartIndex); + double firstLatitude = startPoint.getLatitude().getDouble(); + double firstLongitude = startPoint.getLongitude().getDouble(); + double latitudeDiff = 0.0, longitudeDiff = 0.0; + double totalAltitude = 0; + int numAltitudes = 0; + Altitude.Format altFormat = Config.getUseMetricUnits()?Altitude.Format.METRES:Altitude.Format.FEET; + // loop between start and end points + for (int i=inStartIndex; i<= inEndIndex; i++) + { + DataPoint currPoint = getPoint(i); + latitudeDiff += (currPoint.getLatitude().getDouble() - firstLatitude); + longitudeDiff += (currPoint.getLongitude().getDouble() - firstLongitude); + if (currPoint.hasAltitude()) { + totalAltitude += currPoint.getAltitude().getValue(altFormat); + numAltitudes++; + } + } + int numPoints = inEndIndex - inStartIndex + 1; + double meanLatitude = firstLatitude + (latitudeDiff / numPoints); + double meanLongitude = firstLongitude + (longitudeDiff / numPoints); + Altitude meanAltitude = null; + if (numAltitudes > 0) {meanAltitude = new Altitude((int) (totalAltitude / numAltitudes), altFormat);} + + DataPoint insertedPoint = new DataPoint(new Latitude(meanLatitude, Coordinate.FORMAT_NONE), + new Longitude(meanLongitude, Coordinate.FORMAT_NONE), meanAltitude); + // Make into singleton + insertedPoint.setSegmentStart(true); + DataPoint nextPoint = getNextTrackPoint(inEndIndex+1); + if (nextPoint != null) {nextPoint.setSegmentStart(true);} + // Insert points into track + return insertRange(new DataPoint[] {insertedPoint}, inEndIndex + 1); + } + + /** * Append the specified points to the end of the track * @param inPoints DataPoint objects to add @@ -477,7 +575,7 @@ public class Track } // needs to be scaled again to recalc x, y _scaled = false; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); } @@ -533,6 +631,23 @@ public class Track return _yRange; } + /** + * @return The range of lat values as a DoubleRange object + */ + public DoubleRange getLatRange() + { + if (!_scaled) scalePoints(); + return _latRange; + } + /** + * @return The range of lon values as a DoubleRange object + */ + public DoubleRange getLonRange() + { + if (!_scaled) scalePoints(); + return _longRange; + } + /** * @param inPointNum point index, starting at 0 * @return scaled x value of specified point @@ -582,11 +697,17 @@ public class Track */ public boolean hasData(Field inField, int inStart, int inEnd) { + // Loop over selected point range for (int i=inStart; i<=inEnd; i++) { if (_dataPoints[i].getFieldValue(inField) != null) { - return true; + // Check altitudes and timestamps + if ((inField != Field.ALTITUDE || _dataPoints[i].getAltitude().isValid()) + && (inField != Field.TIMESTAMP || _dataPoints[i].getTimestamp().isValid())) + { + return true; + } } } return false; @@ -602,25 +723,53 @@ public class Track return _mixedData; } + /** + * @return true if track contains any points marked for deletion + */ + public boolean hasMarkedPoints() + { + if (_numPoints < 1) { + return false; + } + // Loop over points looking for any marked for deletion + for (int i=0; i<=_numPoints-1; i++) + { + if (_dataPoints[i] != null && _dataPoints[i].getDeleteFlag()) { + return true; + } + } + // None found + return false; + } + + /** + * Clear all the deletion markers + */ + public void clearDeletionMarkers() + { + for (int i=0; i<_numPoints; i++) + { + _dataPoints[i].setMarkedForDeletion(false); + } + } /** * Collect all the waypoints into the given List * @param inList List to fill with waypoints */ - public void getWaypoints(List inList) + public void getWaypoints(List inList) { // clear list inList.clear(); // loop over points and copy all waypoints into list for (int i=0; i<=_numPoints-1; i++) { - if (_dataPoints[i].isWaypoint()) + if (_dataPoints[i] != null && _dataPoints[i].isWaypoint()) { inList.add(_dataPoints[i]); } } } - // TODO: Make similar method to get list of photos /** @@ -680,11 +829,6 @@ public class Track } _mixedData = hasWaypoint && hasTrackpoint; - // Use medians to centre at 0 - double longMedian = (_longRange.getMaximum() + _longRange.getMinimum()) / 2.0; - double latMedian = (_latRange.getMaximum() + _latRange.getMinimum()) / 2.0; - double longFactor = Math.cos(latMedian / 180.0 * Math.PI); // Function of median latitude - // Loop over points and calculate scales _xValues = new double[getNumPoints()]; _yValues = new double[getNumPoints()]; @@ -695,9 +839,9 @@ public class Track DataPoint point = getPoint(p); if (point != null) { - _xValues[p] = (point.getLongitude().getDouble() - longMedian) * longFactor; + _xValues[p] = MapUtils.getXFromLongitude(point.getLongitude().getDouble()); _xRange.addValue(_xValues[p]); - _yValues[p] = (point.getLatitude().getDouble() - latMedian); + _yValues[p] = MapUtils.getYFromLatitude(point.getLatitude().getDouble()); _yRange.addValue(_yValues[p]); } } @@ -739,6 +883,84 @@ public class Track return nearestPoint; } + /** + * Get the next track point starting from the given index + * @param inStartIndex index to start looking from + * @return next track point, or null if end of data reached + */ + public DataPoint getNextTrackPoint(int inStartIndex) + { + return getNextTrackPoint(inStartIndex, _numPoints, true); + } + + /** + * Get the next track point in the given range + * @param inStartIndex index to start looking from + * @param inEndIndex index to stop looking + * @return next track point, or null if end of data reached + */ + public DataPoint getNextTrackPoint(int inStartIndex, int inEndIndex) + { + return getNextTrackPoint(inStartIndex, inEndIndex, true); + } + + /** + * Get the previous track point starting from the given index + * @param inStartIndex index to start looking from + * @return next track point, or null if end of data reached + */ + public DataPoint getPreviousTrackPoint(int inStartIndex) + { + return getNextTrackPoint(inStartIndex, _numPoints, false); + } + + /** + * Get the next track point starting from the given index + * @param inStartIndex index to start looking from + * @param inEndIndex index to stop looking (inclusive) + * @param inCountUp true for next, false for previous + * @return next track point, or null if end of data reached + */ + private DataPoint getNextTrackPoint(int inStartIndex, int inEndIndex, boolean inCountUp) + { + // Loop forever over points + int increment = inCountUp?1:-1; + for (int i=inStartIndex; i<=inEndIndex; i+=increment) + { + DataPoint point = getPoint(i); + // Exit if end of data reached - there wasn't a track point + if (point == null) {return null;} + if (point.isValid() && !point.isWaypoint()) { + // next track point found + return point; + } + } + return null; + } + + /** + * Shift all the segment start flags in the given range by 1 + * Method used by reverse range and its undo + * @param inStartIndex start of range, inclusive + * @param inEndIndex end of range, inclusive + */ + public void shiftSegmentStarts(int inStartIndex, int inEndIndex) + { + boolean prevFlag = true; + boolean currFlag = true; + for (int i=inStartIndex; i<= inEndIndex; i++) + { + DataPoint point = getPoint(i); + if (point != null && !point.isWaypoint()) + { + // remember flag + currFlag = point.getSegmentStart(); + // shift flag by 1 + point.setSegmentStart(prevFlag); + prevFlag = currFlag; + } + } + } ////////////////// Cloning and replacing /////////////////// @@ -804,7 +1026,7 @@ public class Track _numPoints++; // needs to be scaled again _scaled = false; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); return true; } @@ -837,7 +1059,7 @@ public class Track _numPoints += inPoints.length; // needs to be scaled again _scaled = false; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); return true; } @@ -845,6 +1067,7 @@ public class Track /** * Replace the track contents with the given point array * @param inContents array of DataPoint objects + * @return true on success */ public boolean replaceContents(DataPoint[] inContents) { @@ -854,7 +1077,7 @@ public class Track _dataPoints = inContents; _numPoints = _dataPoints.length; _scaled = false; - _broker.informSubscribers(); + UpdateMessageBroker.informSubscribers(); return true; } @@ -869,17 +1092,27 @@ public class Track { if (inPoint != null && inEditList != null && inEditList.getNumEdits() > 0) { + // remember if coordinates have changed + boolean coordsChanged = false; // go through edits one by one int numEdits = inEditList.getNumEdits(); for (int i=0; i