added null-skipping for Bravo tracks

Change-Id: I26070d5ba98b6916d651271ab9db357f7c9d2384
This commit is contained in:
Axel Uhl
2017-10-26 23:51:51 +02:00
parent 3e9697ce14
commit 374a93d792
8 changed files with 187 additions and 74 deletions
@@ -0,0 +1,44 @@
package com.sap.sailing.domain.common.scalablevalue.impl;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sse.common.scalablevalue.ScalableValue;
import com.sap.sse.common.scalablevalue.ScalableValueWithDistance;
public class ScalableDistance implements ScalableValueWithDistance<Double, Distance> {
private final double meters;
public ScalableDistance(Distance distance) {
this.meters = distance.getMeters();
}
private ScalableDistance(double meters) {
this.meters = meters;
}
@Override
public ScalableDistance multiply(double factor) {
return new ScalableDistance(factor*meters);
}
@Override
public ScalableDistance add(ScalableValue<Double, Distance> t) {
return new ScalableDistance(meters+t.getValue());
}
@Override
public Distance divide(double divisor) {
return new MeterDistance(meters / divisor);
}
@Override
public Double getValue() {
return meters;
}
@Override
public double getDistance(Distance other) {
return Math.abs(meters-other.getMeters());
}
}
@@ -6,9 +6,9 @@ import com.sap.sailing.domain.common.Bearing;
* Extended version of {@link BravoFix} that provides access to more measures found in the extended data format.
*/
public interface BravoExtendedFix extends BravoFix {
double getPortDaggerboardRake();
double getStbdDaggerboardRake();
double getPortRudderRake();
double getStbdRudderRake();
Double getPortDaggerboardRake();
Double getStbdDaggerboardRake();
Double getPortRudderRake();
Double getStbdRudderRake();
Bearing getMastRotation();
}
@@ -24,28 +24,29 @@ public class BravoExtendedFixImpl extends BravoFixImpl implements BravoExtendedF
}
@Override
public double getPortDaggerboardRake() {
public Double getPortDaggerboardRake() {
return fix.get(BravoExtendedSensorDataMetadata.DB_RAKE_PORT.getColumnIndex());
}
@Override
public double getStbdDaggerboardRake() {
public Double getStbdDaggerboardRake() {
return fix.get(BravoExtendedSensorDataMetadata.DB_RAKE_STBD.getColumnIndex());
}
@Override
public double getPortRudderRake() {
public Double getPortRudderRake() {
return fix.get(BravoExtendedSensorDataMetadata.RUDDER_RAKE_PORT.getColumnIndex());
}
@Override
public double getStbdRudderRake() {
public Double getStbdRudderRake() {
return fix.get(BravoExtendedSensorDataMetadata.RUDDER_RAKE_STBD.getColumnIndex());
}
@Override
public Bearing getMastRotation() {
return new DegreeBearingImpl(fix.get(BravoExtendedSensorDataMetadata.MAST_ROTATION.getColumnIndex()));
final Double bearingDeg = fix.get(BravoExtendedSensorDataMetadata.MAST_ROTATION.getColumnIndex());
return bearingDeg == null ? null : new DegreeBearingImpl(bearingDeg);
}
}
@@ -58,11 +58,13 @@ public class BravoFixImpl extends SensorFixImpl implements BravoFix {
@Override
public Bearing getPitch() {
return new DegreeBearingImpl(fix.get(BravoSensorDataMetadata.PITCH.getColumnIndex()));
final Double bearingDeg = fix.get(BravoSensorDataMetadata.PITCH.getColumnIndex());
return bearingDeg == null ? null : new DegreeBearingImpl(bearingDeg);
}
@Override
public Bearing getHeel() {
return new DegreeBearingImpl(fix.get(BravoSensorDataMetadata.HEEL.getColumnIndex()));
final Double bearingDeg = fix.get(BravoSensorDataMetadata.HEEL.getColumnIndex());
return bearingDeg == null ? null : new DegreeBearingImpl(bearingDeg);
}
}
@@ -0,0 +1,14 @@
package com.sap.sailing.domain.tracking;
/**
* A predicate for a fix, for use in
* {@link Track#getInterpolatedValue(com.sap.sse.common.TimePoint, com.sap.sse.common.Util.Function)}, used, e.g., to
* provide a rule for when a fix shall be accepted during the search for surrounding fixes.
*
* @author Axel Uhl (d043530)
*
* @param <FixType>
*/
public interface FixAcceptancePredicate<FixType> {
boolean isAcceptFix(FixType fix);
}
@@ -6,6 +6,7 @@ import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NavigableSet;
import com.sap.sailing.domain.tracking.FixAcceptancePredicate;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
@@ -148,14 +149,25 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
@Override
public FixType getLastFixAtOrBefore(TimePoint timePoint) {
return getLastFixAtOrBefore(timePoint, /* fixAcceptancePredicate == null means accept all */ null);
}
private FixType getLastFixAtOrBefore(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
lockForRead();
try {
return (FixType) getInternalFixes().floor(getDummyFix(timePoint));
final NavigableSet<FixType> headSet = getInternalFixes().headSet(getDummyFix(timePoint), /* inclusive */ true);
for (final Iterator<FixType> i=headSet.descendingIterator(); i.hasNext(); ) {
final FixType next = i.next();
if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
return next;
}
}
return null;
} finally {
unlockAfterRead();
}
}
@Override
public FixType getLastFixBefore(TimePoint timePoint) {
lockForRead();
@@ -188,9 +200,20 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
@Override
public FixType getFirstFixAtOrAfter(TimePoint timePoint) {
return getFirstFixAtOrAfter(timePoint, /* fixAcceptancePredicate==null means accept all fixes */ null);
}
private FixType getFirstFixAtOrAfter(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
lockForRead();
try {
return (FixType) getInternalFixes().ceiling(getDummyFix(timePoint));
final NavigableSet<FixType> tailSet = getInternalFixes().tailSet(getDummyFix(timePoint), /* inclusive */ true);
for (final Iterator<FixType> i=tailSet.iterator(); i.hasNext(); ) {
final FixType next = i.next();
if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
return next;
}
}
return null;
} finally {
unlockAfterRead();
}
@@ -254,9 +277,16 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
}
}
private Pair<FixType, FixType> getSurroundingFixes(TimePoint timePoint) {
FixType left = getLastFixAtOrBefore(timePoint);
FixType right = getFirstFixAtOrAfter(timePoint);
/**
* @param fixAcceptancePredicate
* if not {@code null}, adjacent fixes will be skipped as long as this predicate does not
* {@link FixAcceptancePredicate#isAcceptFix(Object) accept} the fix. This can, e.g., be used to skip
* fixes that don't have values in a dimension required. If {@code null}, the next fixes left and right
* (including the exact {@code timePoint} if a fix exists there) will be used without further check.
*/
private Pair<FixType, FixType> getSurroundingFixes(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
FixType left = getLastFixAtOrBefore(timePoint, fixAcceptancePredicate);
FixType right = getFirstFixAtOrAfter(timePoint, fixAcceptancePredicate);
com.sap.sse.common.Util.Pair<FixType, FixType> result = new com.sap.sse.common.Util.Pair<>(left, right);
return result;
}
@@ -274,9 +304,15 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
}
@Override
public <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint, Function<FixType, ScalableValue<InternalType, ValueType>> converter) {
public <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
Function<FixType, ScalableValue<InternalType, ValueType>> converter) {
return getInterpolatedValue(timePoint, converter, /* fixAcceptancePredicate==null means accept all */ null);
}
protected <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
Function<FixType, ScalableValue<InternalType, ValueType>> converter, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
final ValueType result;
Pair<FixType, FixType> fixPair = getSurroundingFixes(timePoint);
Pair<FixType, FixType> fixPair = getSurroundingFixes(timePoint, fixAcceptancePredicate);
if (fixPair.getA() == null) {
if (fixPair.getB() == null) {
result = null;
@@ -0,0 +1,49 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import java.util.UUID;
import org.junit.Test;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
import com.sap.sailing.domain.common.sensordata.BravoExtendedSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class BravoFixTrackInterpolationWithNullTest {
@Test
public void testFixesWithNonNullFieldsAreSelected() {
DynamicBravoFixTrack<CourseArea> track = new BravoFixTrackImpl<>(new CourseAreaImpl("Test", UUID.randomUUID()),
"test", /* hasExtendedFixes */ true);
track.add(createFix(10l, 5., null));
track.add(createFix(20l, null, 7.));
track.add(createFix(30l, 7., null));
assertEquals(6., track.getHeel(new MillisecondsTimePoint(20l)).getDegrees(), 0.00001);
}
@Test
public void testFixesWithNonNullFieldsAreSelectedForMultipleNullsInARow() {
DynamicBravoFixTrack<CourseArea> track = new BravoFixTrackImpl<>(new CourseAreaImpl("Test", UUID.randomUUID()),
"test", /* hasExtendedFixes */ true);
track.add(createFix(10l, 5., null));
track.add(createFix(20l, null, 7.));
track.add(createFix(30l, null, 7.));
track.add(createFix(40l, null, 7.));
track.add(createFix(50l, 7., null));
assertEquals(6., track.getHeel(new MillisecondsTimePoint(30l)).getDegrees(), 0.00001);
assertEquals(5., track.getHeel(new MillisecondsTimePoint(10l)).getDegrees(), 0.00001);
}
private BravoExtendedFixImpl createFix(long timePointAsMillis, Double heel, Double pitch) {
final Double[] fixData = new Double[Math.max(BravoExtendedSensorDataMetadata.HEEL.getColumnIndex(),
BravoExtendedSensorDataMetadata.PITCH.getColumnIndex())+1];
fixData[BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()] = heel;
fixData[BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()] = pitch;
return new BravoExtendedFixImpl(new DoubleVectorFixImpl(new MillisecondsTimePoint(timePointAsMillis), fixData));
}
}
@@ -8,6 +8,8 @@ import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableDistance;
import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
@@ -44,62 +46,20 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
@Override
public Distance getRideHeight(TimePoint timePoint) {
BravoFix fixAfter = getFirstFixAtOrAfter(timePoint);
if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
return fixAfter.getRideHeight();
}
BravoFix fixBefore = getLastFixAtOrBefore(timePoint);
if (fixBefore != null && fixBefore.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
return fixBefore.getRideHeight();
}
if (fixAfter == null || fixBefore == null) {
// the fix is out of the TimeRange where we have fixes
return null;
}
// TODO interpolate if necessary
return fixBefore.getRideHeight();
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getRideHeight,
ScalableDistance::new);
}
@Override
public Bearing getHeel(TimePoint timePoint) {
BravoFix fixAfter = getFirstFixAtOrAfter(timePoint);
if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
return fixAfter.getHeel();
}
BravoFix fixBefore = getLastFixAtOrBefore(timePoint);
if (fixBefore != null && fixBefore.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
return fixBefore.getHeel();
}
if (fixAfter == null || fixBefore == null) {
// the fix is out of the TimeRange where we have fixes
return null;
}
// TODO interpolate if necessary
return fixBefore.getHeel();
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getHeel,
ScalableBearing::new);
}
@Override
public Bearing getPitch(TimePoint timePoint) {
BravoFix fixAfter = getFirstFixAtOrAfter(timePoint);
if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
return fixAfter.getPitch();
}
BravoFix fixBefore = getLastFixAtOrBefore(timePoint);
if (fixBefore != null && fixBefore.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
return fixBefore.getPitch();
}
if (fixAfter == null || fixBefore == null) {
// the fix is out of the TimeRange where we have fixes
return null;
}
// TODO interpolate if necessary
return fixBefore.getPitch();
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getPitch,
ScalableBearing::new);
}
@Override
@@ -185,18 +145,25 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
* Generic implementation to get values from extended fixes. The implementation ensured that in case of simple
* {@link BravoFix} instances, just {@code null} is returned. If valid {@link BravoExtendedFix BravoExtendedFixes}
* are found, the provided getter is used to extract the value from the identified fix.
* <p>
*
* In case of a mix of {@link BravoFix} and {@link BravoExtendedFix} instances, this method may return null values
* for specific {@link TimePoint TimePoints}.
* <p>
*
* If the value extracted by the {@code getter} is {@code null}, the next fix will be probed, until no more fix
* exists in that direction or a fix is found that delivers a non-{@code null} value for the {@code getter} result.
* This way it is possible to skip fixes that don't make a statement with regard to the attribute extracted by the
* {@code getter}.
*/
private <T, I> T getValueFromExtendedFix(final TimePoint timePoint, final Function<BravoExtendedFix, T> getter,
private <T, I> T getValueFromExtendedFixSkippingNullValues(final TimePoint timePoint, final Function<BravoExtendedFix, T> getter,
Function<T, ScalableValue<I, T>> converterToScalableValue) {
if (!hasExtendedFixes) {
return null;
}
final com.sap.sse.common.Util.Function<BravoFix, ScalableValue<I, T>> converter =
fix -> converterToScalableValue.apply(getter.apply((BravoExtendedFix) fix));
return getInterpolatedValue(timePoint, converter); // TODO bug 4351: provide a "relevance" predicate function to be used by getSurroundingFixes such that, e.g., fixes with null values in the dimension to be retrieved can be skipped
return getInterpolatedValue(timePoint, converter, fix->getter.apply((BravoExtendedFix) fix) != null);
}
public BravoExtendedFix getFirstFixAtOrAfterIfExtended(TimePoint timePoint) {
@@ -211,31 +178,31 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
@Override
public Double getPortDaggerboardRakeIfAvailable(TimePoint timePoint) {
return getValueFromExtendedFix(timePoint, BravoExtendedFix::getPortDaggerboardRake,
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getPortDaggerboardRake,
ScalableDouble::new);
}
@Override
public Double getStbdDaggerboardRakeStbdIfAvailable(TimePoint timePoint) {
return getValueFromExtendedFix(timePoint, BravoExtendedFix::getStbdDaggerboardRake,
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getStbdDaggerboardRake,
ScalableDouble::new);
}
@Override
public Double getPortRudderRakeIfAvailable(TimePoint timePoint) {
return getValueFromExtendedFix(timePoint, BravoExtendedFix::getPortRudderRake,
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getPortRudderRake,
ScalableDouble::new);
}
@Override
public Double getStbdRudderRakeIfAvailable(TimePoint timePoint) {
return getValueFromExtendedFix(timePoint, BravoExtendedFix::getStbdRudderRake,
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getStbdRudderRake,
ScalableDouble::new);
}
@Override
public Bearing getMastRotationIfAvailable(TimePoint timePoint) {
return getValueFromExtendedFix(timePoint, BravoExtendedFix::getMastRotation,
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getMastRotation,
NaivelyScalableBearing::new);
}