package com.github.pfichtner.jrunalyser.base.data.segmenter;
import static com.google.common.base.Functions.compose;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.github.pfichtner.jrunalyser.base.data.DefaultDistance;
import com.github.pfichtner.jrunalyser.base.data.DefaultDuration;
import com.github.pfichtner.jrunalyser.base.data.Distance;
import com.github.pfichtner.jrunalyser.base.data.DistanceUnit;
import com.github.pfichtner.jrunalyser.base.data.DivideTrack;
import com.github.pfichtner.jrunalyser.base.data.Duration;
import com.github.pfichtner.jrunalyser.base.data.LinkedTrackPoint;
import com.github.pfichtner.jrunalyser.base.data.SegmentationUnit;
import com.github.pfichtner.jrunalyser.base.data.Speed;
import com.github.pfichtner.jrunalyser.base.data.floater.FloatingSegmenter;
import com.github.pfichtner.jrunalyser.base.data.segment.DefaultSegment;
import com.github.pfichtner.jrunalyser.base.data.segment.Segment;
import com.github.pfichtner.jrunalyser.base.data.stat.DefaultStatistics;
import com.github.pfichtner.jrunalyser.base.data.stat.Functions;
import com.github.pfichtner.jrunalyser.base.data.stat.StatCalculators;
import com.github.pfichtner.jrunalyser.base.data.stat.StatCalculators.StatCalculator;
import com.github.pfichtner.jrunalyser.base.data.track.DefaultTrack;
import com.github.pfichtner.jrunalyser.base.data.track.Track;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
public final class Segmenters {
/**
* Segments the track into several parts (e.g. based on durations or
* distances).
*
* @author Peter Fichtner
*/
private static abstract class AbtstractDivideTrackSegmenter implements
Segmenter {
private final int parts;
public AbtstractDivideTrackSegmenter(int parts) {
this.parts = parts;
}
/**
* Returns the amount of parts this segmenter segments.
*
* @return amount of parts this segmenter segments
*/
protected int getParts() {
return this.parts;
}
@Override
public final Track segment(Track track) {
Segmenter delegate = createDelegate(track);
Track segmentedTrack = delegate.segment(track);
Track result = segmentedTrack.getSegments().size() > getParts() ? fix(segmentedTrack)
: segmentedTrack;
assert result.getSegments().size() == getParts() : "Track was segmented into "
+ result.getSegments().size()
+ " parts "
+ " but result should have been " + getParts() + " parts";
return result;
}
private Track fix(Track track) {
List<Segment> newSegments = Lists.newArrayList();
if (getParts() > 2) {
newSegments.addAll(track.getSegments().subList(0,
getParts() - 1));
}
// merge last-but-one and last
List<LinkedTrackPoint> wps = mergeLastTwo(track);
newSegments.add(new DefaultSegment(wps, DefaultStatistics
.ofWaypoints(wps)));
return new DefaultTrack(track.getId(), track.getMetadata(),
track.getWaypoints(), newSegments, track.getStatistics());
}
private List<LinkedTrackPoint> mergeLastTwo(Track track) {
List<Segment> segments = track.getSegments();
return new ImmutableList.Builder<LinkedTrackPoint>()
.addAll(segments.get(getParts() - 2).getTrackpoints())
.addAll(segments.get(getParts() - 1).getTrackpoints())
.build();
}
protected abstract Segmenter createDelegate(Track track);
}
private static class DivideTrackSegmenterDuration extends
AbtstractDivideTrackSegmenter {
public DivideTrackSegmenterDuration(int parts) {
super(parts);
}
@Override
protected Segmenter createDelegate(Track track) {
double seconds = track.getStatistics().getDuration()
.getValue(TimeUnit.SECONDS)
/ getParts();
return getSegmenter(DefaultDuration.of(seconds, TimeUnit.SECONDS));
}
}
private static class DivideTrackSegmenterDistance extends
AbtstractDivideTrackSegmenter {
public DivideTrackSegmenterDistance(int parts) {
super(parts);
}
protected Segmenter createDelegate(Track track) {
double meters = track.getStatistics().getDistance()
.getValue(DistanceUnit.METERS)
/ getParts();
return getSegmenter(DefaultDistance.of(meters, DistanceUnit.METERS));
};
}
private Segmenters() {
super();
}
public static Segmenter duration(Duration duration) {
Function<LinkedTrackPoint, Duration> dataFunc = compose(
Functions.Links.duration(), Functions.LinkedWayPoints.link());
return new MathObjectSegmenter<Duration>(duration, DefaultDuration.of(
0, duration.getTimeUnit()), dataFunc);
}
public static Segmenter distance(Distance distance) {
Function<LinkedTrackPoint, Distance> dataFunc = compose(
Functions.Links.distance(), Functions.LinkedWayPoints.link());
return new MathObjectSegmenter<Distance>(distance, DefaultDistance.of(
0, distance.getDistanceUnit()), dataFunc);
}
public static Segmenter getSegmenter(SegmentationUnit unit) {
if (unit == null) {
return Segmenter.NULL_SEGMENTER;
} else if (unit instanceof Duration) {
return duration((Duration) unit);
} else if (unit instanceof Distance) {
return distance((Distance) unit);
} else if (unit instanceof DivideTrack
&& ((DivideTrack) unit).getBasedOn() == Duration.class) {
return new DivideTrackSegmenterDuration(
((DivideTrack) unit).getParts());
} else if (unit instanceof DivideTrack
&& ((DivideTrack) unit).getBasedOn() == Distance.class) {
return new DivideTrackSegmenterDistance(
((DivideTrack) unit).getParts());
}
throw new IllegalStateException("Cannot handle " + unit);
}
// ---------------------------------------------------------------------------
public static Segmenter floatingDuration(Duration duration) {
StatCalculator<Duration> distCalc = StatCalculators
.duration(TimeUnit.MINUTES);
StatCalculator<Speed> valueCalc = StatCalculators.avgSpeed(
DistanceUnit.KILOMETERS, TimeUnit.HOURS);
return new FloatingSegmenter<Duration, Speed>(duration, distCalc,
valueCalc);
}
public static Segmenter floatingDistance(Distance distance) {
StatCalculator<Distance> distCalc = StatCalculators
.distance(DistanceUnit.METERS);
StatCalculator<Speed> valueCalc = StatCalculators.avgSpeed(
DistanceUnit.KILOMETERS, TimeUnit.HOURS);
return new FloatingSegmenter<Distance, Speed>(distance, distCalc,
valueCalc);
}
public static Segmenter getFloatingSegmenter(SegmentationUnit unit) {
if (unit == null) {
return Segmenter.NULL_SEGMENTER;
} else if (unit instanceof Duration) {
return floatingDuration((Duration) unit);
} else if (unit instanceof Distance) {
return floatingDistance((Distance) unit);
}
throw new IllegalStateException("Cannot handle " + unit);
}
}