-package tim.prune.function.compress;
-
-import java.awt.Component;
-import java.awt.event.ActionListener;
-
-import tim.prune.data.DataPoint;
-import tim.prune.data.Track;
-
-/**
- * Douglas-Peucker algorithm for compresssion
- */
-public class DouglasPeuckerAlgorithm extends SingleParameterAlgorithm
-{
- /**
- * Constructor
- * @param inTrack track object
- * @param inDetails track details object
- * @param inListener listener to attach to activation control
- */
- public DouglasPeuckerAlgorithm(Track inTrack, TrackDetails inDetails, ActionListener inListener)
- {
- super(inTrack, inDetails, inListener);
- }
-
- /**
- * Perform the compression and work out which points should be deleted
- * @param inFlags deletion flags from previous algorithms
- * @return number of points deleted
- */
- protected int compress(boolean[] inFlags)
- {
- // Parse parameter
- double param = getParameter();
- // Use 1/x if x greater than 1
- if (param > 1.0) param = 1.0 / param;
- if (param <= 0.0 || param >= 1.0) {
- // Parameter isn't valid, don't delete any
- return 0;
- }
- double threshold = _trackDetails.getTrackSpan() * param;
-
- int numPoints = _track.getNumPoints();
- int origNumDeleted = countFlags(inFlags);
- // Convert inFlags into keepFlags
- int[] keepFlags = new int[numPoints];
- int segStart = -1, segEnd = -1;
- // Loop over all points in track
- for (int i=0; i<numPoints; i++)
- {
- DataPoint currPoint = _track.getPoint(i);
- if (currPoint.getSegmentStart())
- {
- // new segment found, so process previous one
- if (segStart > -1 && segEnd > segStart)
- {
- keepFlags[segEnd] = 1; // keep
- compressSegment(keepFlags, segStart, segEnd, threshold);
- segStart = segEnd = -1;
- }
- }
- if (inFlags[i]) keepFlags[i] = -1; // already deleted
- else if (currPoint.isWaypoint() || currPoint.hasMedia() || currPoint.getSegmentStart()) {
- keepFlags[i] = 1; // keep
- }
- // Don't consider points which are already marked as deleted, ignore waypoints
- if (!inFlags[i] && !currPoint.isWaypoint())
- {
- // remember starts and ends
- if (segStart < 0) {segStart = i;}
- else {segEnd = i;}
- }
- }
- // Last segment, if any
- if (segStart >= 0 && segEnd > segStart) {
- keepFlags[segEnd] = 1; // keep
- compressSegment(keepFlags, segStart, segEnd, threshold);
- }
- // Convert keepFlags back into inFlags
- for (int i=1; i<numPoints; i++) {
- if (keepFlags[i] < 1) inFlags[i] = true;
- }
- return countFlags(inFlags) - origNumDeleted;
- }
-
-
- /**
- * Count the number of true flags in the given array
- * @param inFlags array of boolean flags
- * @return number of flags which are set to true
- */
- private static int countFlags(boolean[] inFlags)
- {
- int numDeleted = 0;
- for (int i=0; i<inFlags.length; i++) {
- if (inFlags[i]) numDeleted++;
- }
- return numDeleted;
- }
-
- /**
- * Compress the given segment (recursively)
- * @param inFlags int array of deletion flags for entire track
- * @param inSegStart index of start of segment
- * @param inSegEnd index of end of segment
- * @param inThreshold threshold to use
- */
- private void compressSegment(int[] inFlags, int inSegStart, int inSegEnd,
- double inThreshold)
- {
- // System.out.println("Compress segment " + inSegStart + "-" + inSegEnd);
- final int numPoints = inSegEnd - inSegStart + 1;
- if (numPoints < 3) {return;} // segment too short to compress
- // Calculate parameters of straight line between first and last
- XYpoint startxy = new XYpoint(_track.getX(inSegStart), _track.getY(inSegStart));
- XYpoint endxy = new XYpoint(_track.getX(inSegEnd), _track.getY(inSegEnd));
- XYpoint ab = startxy.vectorTo(endxy);
- final double dist2AB = ab.len2();
- // create unit vector perpendicular to AB
- final double distAB = ab.len();
- XYpoint perpendicular = new XYpoint(ab.y/distAB, -ab.x/distAB);
- // Check whether distAB is 0.0 - if so, find furthest point from startxy and compress from start to here and here to end
- if (distAB <= 0.0)
- {
- final int furthestIndex = getFurthestPointIndex(inSegStart, inSegEnd);
- if (furthestIndex > inSegStart)
- {
- compressSegment(inFlags, inSegStart, furthestIndex, inThreshold);
- compressSegment(inFlags, furthestIndex, inSegEnd, inThreshold);
- }
- return;
- }
-
- double maxDist = -1.0, dist = -1.0;
- int furthestIndex = -1;
- for (int i=inSegStart+1; i<inSegEnd; i++)
- {
- if (inFlags[i] == 0) // unknown status
- {
- XYpoint currPoint = new XYpoint(_track.getX(i), _track.getY(i));
- XYpoint ac = startxy.vectorTo(currPoint);
- double distAP = ab.dot(ac) / dist2AB;
- // calc distance from point to line depending on distAP
- if (distAP < 0.0) {
- dist = ac.len(); // outside line segment AB on the A side
- }
- else if (distAP > 1.0) {
- dist = endxy.vectorTo(currPoint).len(); // outside on the B side
- }
- else {
- // P lies between A and B so use dot product
- dist = Math.abs(perpendicular.dot(ac));
- }
- if (dist > maxDist)
- {
- maxDist = dist;
- furthestIndex = i;
- }
- }
- }
- // Check furthest point and see if it's further than the threshold
- if (maxDist > inThreshold)
- {
- inFlags[furthestIndex] = 1;
- // Make recursive calls for bit before and bit after kept point
- compressSegment(inFlags, inSegStart, furthestIndex, inThreshold);
- compressSegment(inFlags, furthestIndex, inSegEnd, inThreshold);
- }
- }
-
-
- /**
- * @return specific gui components for dialog
- */
- protected Component getSpecificGuiComponents()
- {
- return getSpecificGuiComponents("dialog.compress.douglaspeucker.paramdesc", "2000");
- }
-
- /**
- * @return title key for box
- */
- protected String getTitleTextKey()
- {
- return "dialog.compress.douglaspeucker.title";
- }
-
- /**
- * Find the index of the point furthest away from the start and end points
- * @param inStartIndex start index of segment to check
- * @param inEndIndex end index of segment to check
- * @return index of furthest point, or -1 if none found
- */
- private int getFurthestPointIndex(int inStartIndex, int inEndIndex)
- {
- int furthestIndex = -1;
- if (inStartIndex >= 0 && inEndIndex > inStartIndex)
- {
- final DataPoint startPoint = _track.getPoint(inStartIndex);
- double maxDist = 0.0;
- // Loop over points between start and end
- for (int i=inStartIndex+1; i<inEndIndex; i++)
- {
- DataPoint p = _track.getPoint(i);
- if (!p.isWaypoint())
- {
- double distFromStart = DataPoint.calculateRadiansBetween(startPoint, p);
- if (distFromStart > maxDist)
- {
- furthestIndex = i;
- maxDist = distFromStart;
- }
- }
- }
- }
- return furthestIndex;
- }
-}