mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-24 14:38:45 +00:00
added null-skipping for Bravo tracks
Change-Id: I26070d5ba98b6916d651271ab9db357f7c9d2384
This commit is contained in:
+44
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -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();
|
||||
}
|
||||
|
||||
+6
-5
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -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);
|
||||
}
|
||||
+44
-8
@@ -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;
|
||||
|
||||
+49
@@ -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));
|
||||
}
|
||||
}
|
||||
+22
-55
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user