content: package tim.prune.function.compress.methods;
  
  import tim.prune.data.DataPoint;
  import tim.prune.data.Distance;
  import tim.prune.data.MarkingData;
  import tim.prune.data.RangeStats;
  import tim.prune.data.Track;
  import tim.prune.data.Unit;
  import tim.prune.data.UnitSetLibrary;
  import tim.prune.function.compress.CompressionMethodType;
  import tim.prune.function.compress.TrackDetails;
  
  /** Remove the parts of the track which look like uphill ski lifts */
  public class SkiLiftsMethod extends ParameterlessMethod
  {
  	private static final long SECONDS_TO_LOOK_FORWARD = 120;
  	private static final int ALLOWED_METRES_DOWNHILL = 20;
  	private static final int MINIMUM_ASCENT_FACTOR = 10;
  	private static final double STRAIGHTNESS_FACTOR = 1.02;
  
  
  	public int compress(Track inTrack, TrackDetails inDetails, MarkingData inMarkings)
  	{
  		final int numPoints = inTrack.getNumPoints();
  		final int numAlreadyDeleted = inMarkings.getNumDeleted();
  		int previousStartIndex = -1;
  		for (int i=0; i<numPoints; i++)
  		{
  			DataPoint currPoint = inTrack.getPoint(i);
  			// Look forwards from point i to point n
  			int n = i+1;
  			DataPoint endPoint = inTrack.getPoint(n);
  			while (endPoint != null
  					&& endPoint.hasAltitude()
  					&& endPoint.hasTimestamp()
  					&& !endPoint.isWaypoint()
  					&& endPoint.getTimestamp().getSecondsSince(currPoint.getTimestamp()) < SECONDS_TO_LOOK_FORWARD)
  			{
  				n++;
  				endPoint = inTrack.getPoint(n);
  			}
  			// Found a suitable range to test with altitudes and timestamps
  			if (looksLikeLiftRange(inTrack, i, n))
  			{
  				// Passes tests, so we want to mark all points between i and n
  				int startIndex = i;
  				// First check if we can merge with the previous marked range
  				if (looksLikeLiftRange(inTrack, previousStartIndex, i)
  					|| looksLikeLiftRange(inTrack, previousStartIndex, n))
  				{
  					startIndex = previousStartIndex; // merge
  				}
  				// Mark all points from startIndex <= x <= n
  				for (int j=startIndex; j<=n; j++) {
  					inMarkings.markPointForDeletion(j, true, true);
  				}
  				// Remember start point for next one
  				previousStartIndex = startIndex;
  				// skip forward half the range, don't need to test the same points again
  				i = (i+n)/2;
  			}
  		}
  		return inMarkings.getNumDeleted() - numAlreadyDeleted;
  	}
  
  	public CompressionMethodType getType() {
  		return CompressionMethodType.SKI_LIFTS;
  	}
  
  	static boolean recogniseString(String inString) {
  		return recogniseString(inString, CompressionMethodType.SKI_LIFTS);
  	}
  
  	/**
  	 * Check whether the specified range looks like an uphill lift section or not
  	 * Must go at most a little bit downhill, much more uphill than down, and straight
  	 * (speed isn't checked yet, but maybe could be?)
  	 * @param inTrack track
  	 * @param inStartIndex start index of range
  	 * @param inEndIndex end index of range
  	 * @return true if it looks like a lift
  	 */
  	private boolean looksLikeLiftRange(Track inTrack, int inStartIndex, int inEndIndex)
  	{
  		// If the start index is negative, we haven't got a proper range (previous index not set)
  		if (inStartIndex < 0) {
  			return false;
  		}
  		// Check whether any points are present which can't be deleted (waypoints, points
  		// without altitude or speed)
  		for (int i=inStartIndex; i<=inEndIndex; i++)
  		{
  			DataPoint point = inTrack.getPoint(i);
  			if (point == null || point.isWaypoint() || !point.hasAltitude() || !point.hasTimestamp()) {
  				return false;
  			}
  		}
  		RangeStats stats = new RangeStats(inTrack, inStartIndex, inEndIndex, 0);
  		Unit unitMetres = UnitSetLibrary.UNITS_METRES;
  		int descent = stats.getTotalAltitudeRange().getDescent(unitMetres);
  		if (descent < ALLOWED_METRES_DOWNHILL)
  		{
  			int ascent = stats.getTotalAltitudeRange().getClimb(unitMetres);
  			if (ascent > (descent * MINIMUM_ASCENT_FACTOR))
  			{
  				// Now check distance and compare to distance between start and end
  				final DataPoint startPoint = inTrack.getPoint(inStartIndex);
  				final DataPoint endPoint = inTrack.getPoint(inEndIndex);
  
  				final double trackDist = stats.getTotalDistance(unitMetres);
  				final double directRadians = DataPoint.calculateRadiansBetween(startPoint, endPoint);
  				final double endToEndDist = Distance.convertRadiansToDistance(directRadians, unitMetres);
  				// Check for straight(ish) line
  				return (trackDist / endToEndDist) < STRAIGHTNESS_FACTOR;
  			}
  		}
  		return false;
  	}
  }
  