From d3ff678786c7d85b1624709c1c75d927c9ab9f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Thu, 10 Jun 2021 16:43:01 +0200 Subject: [PATCH 01/37] bug5568: TimeSlider does not shrink time range if not zoomed in --- .../gwt/client/controls/slider/TimeSlider.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java index c8bdf6025f2..0fe47802542 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java @@ -233,12 +233,21 @@ public class TimeSlider extends SliderBar { this.minValue = minValue; } } - + @Override public boolean setMinAndMaxValue(Double minValue, Double maxValue, boolean fireEvent) { final boolean result; if (!isZoomed) { - result = super.setMinAndMaxValue(minValue, maxValue, fireEvent); + // Inhibit shrinking of the slider ends if not zoomed in + Double minLimited = minValue; + if (minValue != null && this.minValue != null) { + minLimited = Double.min(minValue, this.minValue); + } + Double maxLimited = maxValue; + if (maxValue != null && this.maxValue != null) { + maxLimited = Double.max(maxValue, this.maxValue); + } + result = super.setMinAndMaxValue(minLimited, maxLimited, fireEvent); } else { boolean minChanged = !Util.equalsWithNull(this.minValue, minValue); this.minValue = minValue; @@ -248,7 +257,7 @@ public class TimeSlider extends SliderBar { } return result; } - + @Override protected void onMinMaxValueChanged(boolean fireEvent) { calculateTicks(); From e33f77225fc98ba02e819fb4b71fedb559384eb5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 10 Jun 2021 18:32:18 +0200 Subject: [PATCH 02/37] bug5576: added failing test case for the 30s interval rule --- .../com/sap/sailing/domain/test/WindTest.java | 31 +++++++++++++++++++ .../TrackBasedEstimationWindTrackImpl.java | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index 01bde6add93..4fda840c3f9 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -68,6 +68,7 @@ import com.sap.sailing.domain.tracking.impl.WindTrackImpl; import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl; import com.sap.sse.common.Bearing; import com.sap.sse.common.Color; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; @@ -95,6 +96,36 @@ public class WindTest { PositionAssert.assertBearingEquals(new DegreeBearingImpl(0), average.getBearing().getDifferenceTo(new DegreeBearingImpl(0)), 0.01); } + /** + * When a wind track has a gap, e.g., because a sensor temporarily did not transmit data, averaging should still try to + * obtain fixes "left and right" of the time point requested up to the averaging interval set on the track, ideally + * symmetrically, and taking at least one fix before and one fix after the time point requested, if possible, rather than + * considering a long gap on one side an exceeding of the averaging interval although only one fix is consumed from that + * side.

+ * + * See also bug 5576. + */ + @Test + public void testAveragingWindWithGapInTrack() throws InterruptedException { + WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); + TimePoint t = MillisecondsTimePoint.now(); + TimePoint t1_left = t.minus(Duration.ONE_SECOND); + TimePoint t2_left = t1_left.minus(Duration.ONE_MINUTE); + TimePoint t1_right = t.plus(Duration.ONE_SECOND); + TimePoint t2_right = t1_right.plus(Duration.ONE_MINUTE); + DegreePosition pos = new DegreePosition(0, 0); + Wind wind1 = new WindImpl(pos, t2_left, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(340))); + Wind wind2 = new WindImpl(pos, t1_left, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(350))); + Wind wind3 = new WindImpl(pos, t1_right, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(10))); + Wind wind4 = new WindImpl(pos, t2_right, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(20))); + track.add(wind1); + track.add(wind2); + track.add(wind3); + track.add(wind4); + Wind average = track.getAveragedWind(pos, t); + PositionAssert.assertBearingEquals(new DegreeBearingImpl(0), average.getBearing().getDifferenceTo(new DegreeBearingImpl(0)), 0.01); + } + @Test public void testMultipleWindFixesWithSameTimestampInSameWindTrack() { WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index 957dd51d946..18b6c374e65 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -84,7 +84,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { private static final SpeedWithBearing defaultSpeedWithBearing = new KnotSpeedWithBearingImpl(0, new DegreeBearingImpl(0)); - private static Duration RESOLUTION = Duration.ONE_SECOND; + private final static Duration RESOLUTION = Duration.ONE_SECOND; private final EstimatedWindFixesAsNavigableSet virtualInternalRawFixes; From 4645288af7eea39fa497e767f3c5b7c09f3653af Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 11 Jun 2021 12:31:02 +0200 Subject: [PATCH 03/37] bug5576: started with updating the Javadocs of the getWindUnsynchronized method in WindTrackImpl --- .../domain/tracking/impl/WindTrackImpl.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index 232717c9110..912295d4ec6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -225,13 +225,30 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { /** * This method implements the functionality of the {@link #getAveragedWind(Position, TimePoint)} interface method. - * It does so by collecting (smoothened, outliers removed) wind fixes around the at time point up to - * an interval length as specified by {@link #getMillisecondsOverWhichToAverageWind()}. At least one fix that is - * closest to at will be picked up. If the track is empty, null is returned. Otherwise, - * the wind fixes in the interval constructed are averaged using a {@link ConfidenceBasedWindAverager}. - * + * It does so by collecting (smoothened, outliers removed) wind fixes around the at time point up to an + * interval length as specified by {@link #getMillisecondsOverWhichToAverageWind()}. If available, at least the fix + * time-wise closest before and the fix time-wise closest after {@code at} will be picked up, which may lead to an + * overall interval length that exceeds {@link #getMillisecondsOverWhichToAverageWind()}. *

- * However, not being synchronized, it does not obtain this object's monitor. Subclasses may use this + * + * Collecting the fixes around {@code at} tries to work symmetrically. The interval is counted from the earliest fix + * used to the latest fix used, and always including {@code at}. After adding the latest fix before and the earliest + * fix after {@code at} (if they exist) to the result, while the interval that has to include {@code at} does not + * yet exceed {@link #getMillisecondsOverWhichToAverageWind()}, the next fix that is closest to {@code at} and that + * is not yet part of the result is added unless it would extend the interval beyond + * {@link #getMillisecondsOverWhichToAverageWind()} in which case the iteration ends. + *

+ * + * While this procedure does not guarantee an equal number of fixes on both sides of {@code at}, it works well for + * producing fixes that are aligned closely around {@code at} while also guaranteeing that fixes from both sides of + * {@code at} are considered when available. + *

+ * + * If the track is empty, {@code null} is returned. Otherwise, the wind fixes in the interval constructed are + * averaged using a {@link ConfidenceBasedWindAverager}. + *

+ * + * Not being synchronized, it does not obtain this object's monitor. Subclasses may use this * carefully if they can guarantee there are no concurrency issues with the internal fixes while iterating over the * result of {@link #getInternalFixes()}. * From 43f38b0cc992e7f8954ac975ce93b9ee9d3f3c4e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 11 Jun 2021 15:47:39 +0200 Subject: [PATCH 04/37] bug5576: fixes part 1) and now always obtains one fix per direction if possible, filling up to at most 30s --- .../com/sap/sailing/domain/test/WindTest.java | 27 ++++--- .../domain/tracking/impl/WindTrackImpl.java | 71 ++++++++++++------- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index 4fda840c3f9..e599d89aec4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -177,8 +177,8 @@ public class WindTest { /** * If the wind track has areas with no data, and wind information is requested for such an interval, - * it is essential to still average over the {@link #AVERAGING_INTERVAL_MILLIS} interval, even if the - * interval is further away than {@link #AVERAGING_INTERVAL_MILLIS}. + * it is essential to still average over at least one fix left and one fix right, even if the + * resulting interval is longer than {@link #AVERAGING_INTERVAL_MILLIS}. */ @Test public void testAveragingOfSparseWindTrack() { @@ -198,19 +198,24 @@ public class WindTest { track.add(wind5); track.add(wind6); track.add(wind7); - - // interval does bearely reach 20's burst because 0 has 0 length and 1000..30000 has 29000 length + // expecting to pick up 0, 1000, 2000, 10000 and 30000, all with 20deg PositionAssert.assertSpeedEquals(new KnotSpeedImpl(20), track.getAveragedWind(pos, new MillisecondsTimePoint(1)), 0.02); - // interval uses the two fixes to the left (0, 1000)=1000 and three to the right (2000, 10000, 30000)=28000 + // interval uses the two fixes to the left (0, 1000) and three to the right (2000, 10000, 30000), total length 30000 PositionAssert.assertSpeedEquals(new KnotSpeedImpl(20), track.getAveragedWind(pos, new MillisecondsTimePoint(1001)), 0.02); - // in the middle of the "hole", fetches (0, 1000, 2000, 10000)=10000 and (30000, 40000)=10000, so 20000ms worth of wind + // in the middle of the "hole", fetches 10000 and 30000; then adding 2000 because it's closer to 20000 than 40000; then + // adding 1000 because it's still closer to 20000 than 40000 and still within the 30s range, and the same for 0; but not + // adding 40000 then because 2000..40000=38000 > 30000; still all 20deg values final double averageFor20000 = track.getAveragedWind(pos, new MillisecondsTimePoint(20000)).getKnots(); // value is hard to predict exactly because time difference-based confidences rate fixes closer to 20000ms higher than those further away - assertEquals(35, averageFor20000, 5); - // right of the middle of the "hole", fetches (0, 1000, 2000, 10000)=10000 and (30000, 40000, 50000)=20000 - final double averageFor20500 = track.getAveragedWind(pos, new MillisecondsTimePoint(20500)).getKnots(); - assertEquals(37, averageFor20500, 5); - assertTrue(averageFor20500 > averageFor20000); + assertEquals(20, averageFor20000, 0.02); + // right of the middle of the "hole", fetches 10000 and 30000 initially; then 40000 (distance 16000 is less than distance 22000 to 2000), + // ending up with 10000..40000=30000, so no more fix is picked up; weighted average of two 20deg fixes and one 130deg fix + final double averageFor24000 = track.getAveragedWind(pos, new MillisecondsTimePoint(24000)).getKnots(); + assertEquals(46, averageFor24000, 5); + assertTrue(averageFor24000 > averageFor20000); + // at least one fix will be picked up even when asking later than 30s after the last: + final double averageFor100000 = track.getAveragedWind(pos, new MillisecondsTimePoint(100000)).getKnots(); + assertEquals(170, averageFor100000, 0.02); } @Test diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index 912295d4ec6..21480439e10 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -32,6 +32,7 @@ import com.sap.sse.common.Speed; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Timed; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.shared.util.impl.ArrayListNavigableSet; /** @@ -273,52 +274,64 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { Iterator afterIter = afterSet.iterator(); long beforeDistanceToAt = 0; long afterDistanceToAt = 0; - TimePoint beforeIntervalEnd = null; - TimePoint afterIntervalStart = null; - long beforeIntervalLength = 0; - long afterIntervalLength = 0; Wind beforeWind = null; + Wind afterWind = null; + // pick one left if possible if (beforeIter.hasNext()) { beforeWind = beforeIter.next(); beforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); + windFixesToAverage.add(createWindWithConfidence(beforeWind)); } - Wind afterWind = null; + // pick one right if possible if (afterIter.hasNext()) { afterWind = afterIter.next(); afterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); + windFixesToAverage.add(createWindWithConfidence(afterWind)); } - do { - if (beforeWind != null && (beforeDistanceToAt <= afterDistanceToAt || afterWind == null)) { - windFixesToAverage.add(new WindWithConfidenceImpl>(beforeWind, - getConfidenceOfInternalWindFixUnsynchronized(beforeWind), new Util.Pair(beforeWind.getPosition(), beforeWind - .getTimePoint()), useSpeed)); - if (beforeIntervalEnd == null) { - beforeIntervalEnd = beforeWind.getTimePoint(); - } + long newBeforeDistanceToAt; + long newAfterDistanceToAt; + if (beforeIter.hasNext()) { + beforeWind = beforeIter.next(); + newBeforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); + } else { + beforeWind = null; + newBeforeDistanceToAt = beforeDistanceToAt; + } + if (afterIter.hasNext()) { + afterWind = afterIter.next(); + newAfterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); + } else { + afterWind = null; + newAfterDistanceToAt = afterDistanceToAt; + } + boolean pickBefore; + // Invariant: beforeWind and afterWind each represent the next element in the respective direction that has not yet been consumed, + // beforeDistanceToAt and afterDistanceToAt refer to the fixes consumed to far, newBeforeDistanceToAt and newAfterDistanceToAt + // refer to the yet unconsumed next fix in the corresponding direction. + while ((pickBefore=(beforeWind != null && (afterWind == null || newBeforeDistanceToAt <= newAfterDistanceToAt) && newBeforeDistanceToAt + afterDistanceToAt <= getMillisecondsOverWhichToAverageWind())) + || (afterWind != null && beforeDistanceToAt + newAfterDistanceToAt <= getMillisecondsOverWhichToAverageWind())) { + if (pickBefore) { + windFixesToAverage.add(createWindWithConfidence(beforeWind)); + beforeDistanceToAt = newBeforeDistanceToAt; if (beforeIter.hasNext()) { beforeWind = beforeIter.next(); - beforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); - beforeIntervalLength = beforeIntervalEnd.asMillis() - beforeWind.getTimePoint().asMillis(); + newBeforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); } else { beforeWind = null; + newBeforeDistanceToAt = beforeDistanceToAt; } - } else if (afterWind != null) { - windFixesToAverage.add(new WindWithConfidenceImpl>(afterWind, - getConfidenceOfInternalWindFixUnsynchronized(afterWind), new Util.Pair(afterWind.getPosition(), afterWind - .getTimePoint()), useSpeed)); - if (afterIntervalStart == null) { - afterIntervalStart = afterWind.getTimePoint(); - } + } else { + windFixesToAverage.add(createWindWithConfidence(afterWind)); + afterDistanceToAt = newAfterDistanceToAt; if (afterIter.hasNext()) { afterWind = afterIter.next(); - afterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); - afterIntervalLength = afterWind.getTimePoint().asMillis() - afterIntervalStart.asMillis(); + newAfterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); } else { afterWind = null; + newAfterDistanceToAt = afterDistanceToAt; } } - } while (beforeIntervalLength + afterIntervalLength < getMillisecondsOverWhichToAverageWind() - && (beforeWind != null || afterWind != null)); + } if (windFixesToAverage.isEmpty()) { return null; } else { @@ -330,6 +343,12 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } } + private WindWithConfidenceImpl> createWindWithConfidence(Wind wind) { + return new WindWithConfidenceImpl>(wind, + getConfidenceOfInternalWindFixUnsynchronized(wind), new Util.Pair(wind.getPosition(), wind + .getTimePoint()), useSpeed); + } + /** * Gets the confidence for the specified wind fix. Not being synchronized, it does not obtain this * object's monitor. Subclasses may use this carefully if they can guarantee there are no concurrency issues with From 2198a8a7050650fe3af508e527e613bb7a92dcb8 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 11 Jun 2021 18:42:57 +0200 Subject: [PATCH 05/37] bug5576: make an effort to find an estimated wind fix in the vicinity; helps to smoothen a bit; --- .../TrackBasedEstimationWindTrackImpl.java | 111 +++++++++++++++--- 1 file changed, 93 insertions(+), 18 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index 18b6c374e65..e22b16be588 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -3,10 +3,12 @@ package com.sap.sailing.domain.tracking.impl; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.Set; @@ -28,6 +30,8 @@ import com.sap.sailing.domain.common.impl.WindImpl; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.common.tracking.SensorFix; +import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; +import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.tracking.AddResult; import com.sap.sailing.domain.tracking.DynamicSensorFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; @@ -265,7 +269,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { + trackedRace.getRace().getName(), /* fair */false); virtualInternalRawFixes = new EstimatedWindFixesAsNavigableSet(trackedRace); weigher = new PositionAndTimePointWeigher( - /* halfConfidenceAfterMilliseconds */WindTrack.WIND_HALF_CONFIDENCE_TIME_MILLIS, WindTrack.WIND_HALF_CONFIDENCE_DISTANCE); + /* halfConfidenceAfterMilliseconds */getMillisecondsOverWhichToAverageWind() / 10, WindTrack.WIND_HALF_CONFIDENCE_DISTANCE); listener = new CacheInvalidationRaceChangeListener(); trackedRace.addListener(listener); // in particular, race status changes will be notified, unblocking waiting computations after LOADING phase this.timePointsWithCachedNullResult = new ArrayListNavigableSet( @@ -757,11 +761,18 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { /** * As opposed to the superclass implementation, this variant checks if the {@link EstimatedWindFixesAsNavigableSet#floor(Wind)} - * or {@link EstimatedWindFixesAsNavigableSet#ceiling(Wind)} is closer to at and returns the wind fix with confidence - * from {@link #virtualInternalRawFixes} for the resolution-compliant time point closer to at.

+ * or {@link EstimatedWindFixesAsNavigableSet#ceiling(Wind)} is closer to at and obtains the wind fix with confidence + * from {@link #virtualInternalRawFixes} for the resolution-compliant time point closer to at. If no estimated + * wind fix can be obtained for that time point, the track is scanned left and right of the {@code at} time point, trying + * to obtain at least one, better two adjacent fixes. The search is performed based on this virtual track's {@link #RESOLUTION} + * up to {@link #getMillisecondsOverWhichToAverageWind}/2 left and right of {@code at}. If no estimation can be produced then + * the next available estimation outside of that interval is fetched from the cache for each direction, and the weighted + * average of those is returned.

*/ @Override - protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { + protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { + final WindWithConfidence> result; + final Pair relativeTo = new Pair(p, at); lockForRead(); try { TimePoint floorTimePoint = virtualInternalRawFixes.floorToResolution(at); @@ -773,21 +784,85 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { } else { timePoint = virtualInternalRawFixes.ceilingToResolution(at); } - WindWithConfidence preResult = virtualInternalRawFixes.getWindWithConfidence(timePoint); - // reduce confidence depending on how far *at* is away from the time point of the fix obtained - double confidenceMultiplier = preResult == null ? 0 : weigher.getConfidence( - new Pair(preResult.getObject().getPosition(), timePoint), - new Pair(p, at)); - WindWithConfidenceImpl> result = preResult == null ? null - : new WindWithConfidenceImpl>(preResult.getObject(), confidenceMultiplier - * preResult.getConfidence(), - /* relativeTo */new Util.Pair(p, at), preResult.useSpeed()); + final WindWithConfidence fixAtTimePoint = virtualInternalRawFixes.getWindWithConfidence(timePoint); + ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE.createWindAverager(weigher); + List>> windFixesToAverage = new ArrayList>>(); + if (fixAtTimePoint == null) { + final WindWithConfidence beforeAt = getWindWithConfidenceBefore(timePoint); + final WindWithConfidence afterAt = getWindWithConfidenceAfter(timePoint); + if (beforeAt != null) { + windFixesToAverage.add(createWindWithConfidenceForPositionAndTime(beforeAt)); + } + if (afterAt != null) { + windFixesToAverage.add(createWindWithConfidenceForPositionAndTime(afterAt)); + } + } else { + windFixesToAverage.add(createWindWithConfidenceForPositionAndTime(fixAtTimePoint)); + } + result = windAverager.getAverage(windFixesToAverage, relativeTo); return result; } finally { unlockAfterRead(); } } + + private WindWithConfidenceImpl> createWindWithConfidenceForPositionAndTime( + final WindWithConfidence preResult) { + return new WindWithConfidenceImpl>(preResult.getObject(), + preResult.getConfidence(), new Pair<>(preResult.getObject().getPosition(), preResult.getObject().getTimePoint()), + preResult.useSpeed()); + } + private WindWithConfidence getWindWithConfidenceBefore(TimePoint timePoint) { + final TimePoint leftBump = getTrackedRace().getTimePointOfOldestEvent(); + final WindWithConfidence result; + if (leftBump == null) { + result = null; + } else { + final TimePoint cutoffForRecalculation = timePoint.minus(getMillisecondsOverWhichToAverageWind()/2); + TimePoint t = virtualInternalRawFixes.lowerToResolution(timePoint); + WindWithConfidence candidate = null; + while (candidate == null && !t.before(leftBump)) { + if (t.before(cutoffForRecalculation)) { + // pick the next best candidate from the cache + candidate = cache.floor(new WindWithConfidenceImpl(virtualInternalRawFixes.createDummyWindFix(t), + 0.0, /* relativeTo */ null, /* useSpeed */ false)); + break; + } else { + candidate = virtualInternalRawFixes.getWindWithConfidence(t); + t = virtualInternalRawFixes.lowerToResolution(t); + } + } + result = candidate; + } + return result; + } + + private WindWithConfidence getWindWithConfidenceAfter(TimePoint timePoint) { + final TimePoint rightBump = getTrackedRace().getTimePointOfNewestEvent(); + final WindWithConfidence result; + if (rightBump == null) { + result = null; + } else { + final TimePoint cutoffForRecalculation = timePoint.plus(getMillisecondsOverWhichToAverageWind()/2); + TimePoint t = virtualInternalRawFixes.higherToResolution(timePoint); + WindWithConfidence candidate = null; + while (candidate == null && !t.after(rightBump)) { + if (t.after(cutoffForRecalculation)) { + // pick the next best candidate from the cache + candidate = cache.ceiling(new WindWithConfidenceImpl(virtualInternalRawFixes.createDummyWindFix(t), + 0.0, /* relativeTo */ null, /* useSpeed */ false)); + break; + } else { + candidate = virtualInternalRawFixes.getWindWithConfidence(t); + t = virtualInternalRawFixes.higherToResolution(t); + } + } + result = candidate; + } + return result; + } + @Override public String toString() { lockForRead(); @@ -819,10 +894,6 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { this(trackedRace, null, null); } - protected TrackBasedEstimationWindTrackImpl getTrack() { - return (TrackBasedEstimationWindTrackImpl) super.getTrack(); - } - /** * @param from expected to be an integer multiple of {@link #getResolutionInMilliseconds()} or null * @param to expected to be an integer multiple of {@link #getResolutionInMilliseconds()} or null @@ -831,7 +902,11 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { TimePoint from, TimePoint to) { super(TrackBasedEstimationWindTrackImpl.this, trackedRace, from, to, RESOLUTION.asMillis()); } - + + protected TrackBasedEstimationWindTrackImpl getTrack() { + return (TrackBasedEstimationWindTrackImpl) super.getTrack(); + } + @Override protected Wind getWind(Position p, TimePoint timePoint) { final WindWithConfidence estimatedWindDirectionWithConfidence = getWindWithConfidence(timePoint); From 62b47e5d9ff2c51ee7ee126484deacf6944edd1c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Jun 2021 18:49:04 +0200 Subject: [PATCH 06/37] in TrackedRaceImpl.getWindWithConfidence(Position p, TimePoint at, Set windSourcesToExclude) collect fixes and smoothen --- .../domain/tracking/impl/TrackedRaceImpl.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index dda25654846..27aa40e5110 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -183,6 +183,16 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName()); + /** + * The resolution at which {@link #getWind(Position, TimePoint)} and {@link #getWind(Position, TimePoint, Set)} and + * {@link #getWindWithConfidence(Position, TimePoint)} and {@link #getWindWithConfidence(Position, TimePoint, Set)} + * traverse an interval of length {@link #getMillisecondsOverWhichToAverageWind()}ms around a given time point + * in order to compute a smoothened average for a wind vector. This is then also the basis for the so-called + * "combined" wind and the "leg middle" wind (a specialization of the "combined" wind computed for a different + * position). + */ + private static final Duration WIND_TRACK_RESOLUTION_FOR_SMOOTHENING = Duration.ONE_SECOND; + // TODO make this variable private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500; @@ -2016,9 +2026,21 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, - Set windSourcesToExclude) { - return shortTimeWindCache.getWindWithConfidence(p, at, windSourcesToExclude); + public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, Set windSourcesToExclude) { + final Weigher> timeWeigher = (pair1, pair2)-> + ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(WindTrack.WIND_HALF_CONFIDENCE_TIME_MILLIS).getConfidence(pair1.getB(), pair2.getB()); + // at a 1s interval (or whatever WIND_TRACK_RESOLUTION_FOR_SMOOTHENING says) go to earlier and later time points starting + // from "at" and collect the wind fixes averaged over the wind sources to use, then average them based on time point only + ConfidenceBasedWindAverager> averager = ConfidenceFactory.INSTANCE.createWindAverager(timeWeigher); + final Set>> fixes = new HashSet<>(); + final TimePoint end = at.plus(getMillisecondsOverWhichToAverageWind()/2); + for (TimePoint t=at.minus(getMillisecondsOverWhichToAverageWind()/2); !t.after(end); t=t.plus(WIND_TRACK_RESOLUTION_FOR_SMOOTHENING)) { + final WindWithConfidence> windWithConfidence = shortTimeWindCache.getWindWithConfidence(p, t, windSourcesToExclude); + if (windWithConfidence != null) { + fixes.add(windWithConfidence); + } + } + return averager.getAverage(fixes, new Pair<>(p, at)); } public WindWithConfidence> getWindWithConfidenceUncached(Position p, TimePoint at, From 08506709659d0717938bf2b41c7b48350184c816 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jun 2021 18:05:42 +0200 Subject: [PATCH 07/37] bug5576: reverted to a single read-out for TrackedRaceImpl.getWindWithConfidence but average track-based estimation across 30s --- .../sailing/domain/common/WindSourceType.java | 4 +- .../common/confidence/ConfidenceFactory.java | 3 + .../ConfidenceBasedAveragerFactoryImpl.java | 6 + .../impl/PositionAndTimePointWeigher.java | 4 +- ...dardDistributionTimeDifferenceWeigher.java | 23 ++++ .../TrackBasedEstimationWindTrackImpl.java | 127 ------------------ .../domain/tracking/impl/TrackedRaceImpl.java | 37 ++--- .../domain/tracking/impl/WindTrackImpl.java | 15 ++- ...talMstHmmWindEstimationForTrackedRace.java | 3 +- 9 files changed, 60 insertions(+), 162 deletions(-) create mode 100755 java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/StandardDistributionTimeDifferenceWeigher.java diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSourceType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSourceType.java index 360a3afad83..895a67f92f3 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSourceType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSourceType.java @@ -26,13 +26,13 @@ public enum WindSourceType { * because at a given time point all boats may sail on the same tack and hence no averaging between the * two tacks is possible. This is the more likely to happen the smaller the fleet tracked is. */ - TRACK_BASED_ESTIMATION(false, 0.5, /* useSpeed */ false), + TRACK_BASED_ESTIMATION(false, 0.1, /* useSpeed */ false), /** * Estimates wind conditions by analyzing the maneuvers of all boat tracks closely interoperating with incremental * maneuver detector. */ - MANEUVER_BASED_ESTIMATION(false, 0.1, /* useSpeed */ false), + MANEUVER_BASED_ESTIMATION(false, 0.001, /* useSpeed */ false), /** * Inferred from the race course layout if the course is known to have its first leg be an upwind leg. This diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/ConfidenceFactory.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/ConfidenceFactory.java index 4b5ccd56e07..e6b3551b1ee 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/ConfidenceFactory.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/ConfidenceFactory.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.common.confidence; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.confidence.impl.ConfidenceBasedAveragerFactoryImpl; import com.sap.sse.common.Distance; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; public interface ConfidenceFactory { @@ -33,6 +34,8 @@ public interface ConfidenceFactory { Weigher createHyperbolicTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds); + Weigher createStandardDistributionTimeDifferenceWeigher(Duration standardDeviation); + Weigher createHyperbolicSquaredTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds); /** diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/ConfidenceBasedAveragerFactoryImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/ConfidenceBasedAveragerFactoryImpl.java index 278ceba6c71..63de936fa92 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/ConfidenceBasedAveragerFactoryImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/ConfidenceBasedAveragerFactoryImpl.java @@ -5,6 +5,7 @@ import com.sap.sailing.domain.common.confidence.ConfidenceBasedAverager; import com.sap.sailing.domain.common.confidence.ConfidenceFactory; import com.sap.sailing.domain.common.confidence.Weigher; import com.sap.sse.common.Distance; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; public class ConfidenceBasedAveragerFactoryImpl implements ConfidenceFactory { @@ -30,6 +31,11 @@ public class ConfidenceBasedAveragerFactoryImpl implements ConfidenceFactory { return new HyperbolicTimeDifferenceWeigher(halfConfidenceAfterMilliseconds); } + @Override + public Weigher createStandardDistributionTimeDifferenceWeigher(Duration standardDeviation) { + return new StandardDistributionTimeDifferenceWeigher(standardDeviation); + } + @Override public Weigher createHyperbolicDistanceWeigher(Distance halfConfidence) { return new HyperbolicDistanceWeigher(halfConfidence); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java index e76032899a2..c9736434ef7 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java @@ -6,6 +6,7 @@ import com.sap.sailing.domain.common.confidence.Weigher; import com.sap.sse.common.Distance; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsDurationImpl; /** * A weigher that uses a {@link Position} and a {@link TimePoint} to compute a confidence based on @@ -26,7 +27,8 @@ public class PositionAndTimePointWeigher implements Weigher { + private static final long serialVersionUID = 4378168079868145134L; + private final double standardDeviationAsMillis; + private final double oneDividedByStandardDeviationTimesSquareRootOfTwoPi; + + public StandardDistributionTimeDifferenceWeigher(Duration standardDeviation) { + this.standardDeviationAsMillis = standardDeviation.asMillis(); + this.oneDividedByStandardDeviationTimesSquareRootOfTwoPi = 1/(this.standardDeviationAsMillis*Math.sqrt(2*Math.PI)); + } + + @Override + public double getConfidence(TimePoint fix, TimePoint request) { + double xMinusMu = Math.abs(fix.asMillis() - request.asMillis()); + double xMinusMuDividedByStandardDeviation = xMinusMu/standardDeviationAsMillis; + return oneDividedByStandardDeviationTimesSquareRootOfTwoPi * Math.exp(-0.5*xMinusMuDividedByStandardDeviation*xMinusMuDividedByStandardDeviation); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index e22b16be588..690d032e9a0 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -3,12 +3,10 @@ package com.sap.sailing.domain.tracking.impl; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.Set; @@ -23,15 +21,11 @@ import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.confidence.Weigher; -import com.sap.sailing.domain.common.confidence.impl.PositionAndTimePointWeigher; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.WindImpl; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.common.tracking.SensorFix; -import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; -import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.tracking.AddResult; import com.sap.sailing.domain.tracking.DynamicSensorFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; @@ -42,8 +36,6 @@ import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; -import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.AbstractTimePoint; import com.sap.sse.common.impl.DegreeBearingImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; @@ -109,8 +101,6 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { */ private final NamedReentrantReadWriteLock scheduledRefreshIntervalLock; - private final Weigher> weigher; - /** * A copy of the {@link #timePointsWithCachedNullResult} contents offering fast contains checks. */ @@ -268,8 +258,6 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { TrackBasedEstimationWindTrackImpl.class.getSimpleName() + " scheduledRefreshIntervalLock for race " + trackedRace.getRace().getName(), /* fair */false); virtualInternalRawFixes = new EstimatedWindFixesAsNavigableSet(trackedRace); - weigher = new PositionAndTimePointWeigher( - /* halfConfidenceAfterMilliseconds */getMillisecondsOverWhichToAverageWind() / 10, WindTrack.WIND_HALF_CONFIDENCE_DISTANCE); listener = new CacheInvalidationRaceChangeListener(); trackedRace.addListener(listener); // in particular, race status changes will be notified, unblocking waiting computations after LOADING phase this.timePointsWithCachedNullResult = new ArrayListNavigableSet( @@ -305,17 +293,6 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { trackedRace.getMillisecondsOverWhichToAverageWind() / 2); } - /** - * The track-based estimation already averages the boats' bearings over time before averaging those across legs and - * tacks. There is no use in again averaging over a longer period of time. Therefore, we set the averaging interval - * to two times the resolution of the {@link EstimatedWindFixesAsNavigableSet virtual fixes collection} plus two - * milliseconds, so that at most one fix before and one fix after the time point requested will be used. - */ - @Override - public long getMillisecondsOverWhichToAverageWind() { - return 2*getInternalRawFixes().getResolutionInMilliseconds()+2; - } - @Override protected VirtualWindFixesAsNavigableSet getInternalRawFixes() { return virtualInternalRawFixes; @@ -759,110 +736,6 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { } } - /** - * As opposed to the superclass implementation, this variant checks if the {@link EstimatedWindFixesAsNavigableSet#floor(Wind)} - * or {@link EstimatedWindFixesAsNavigableSet#ceiling(Wind)} is closer to at and obtains the wind fix with confidence - * from {@link #virtualInternalRawFixes} for the resolution-compliant time point closer to at. If no estimated - * wind fix can be obtained for that time point, the track is scanned left and right of the {@code at} time point, trying - * to obtain at least one, better two adjacent fixes. The search is performed based on this virtual track's {@link #RESOLUTION} - * up to {@link #getMillisecondsOverWhichToAverageWind}/2 left and right of {@code at}. If no estimation can be produced then - * the next available estimation outside of that interval is fetched from the cache for each direction, and the weighted - * average of those is returned.

- */ - @Override - protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { - final WindWithConfidence> result; - final Pair relativeTo = new Pair(p, at); - lockForRead(); - try { - TimePoint floorTimePoint = virtualInternalRawFixes.floorToResolution(at); - TimePoint timePoint; - if (floorTimePoint.equals(at) - || Math.abs(floorTimePoint.asMillis() - at.asMillis()) < Math.abs(virtualInternalRawFixes - .ceilingToResolution(at).asMillis() - at.asMillis())) { - timePoint = floorTimePoint; - } else { - timePoint = virtualInternalRawFixes.ceilingToResolution(at); - } - final WindWithConfidence fixAtTimePoint = virtualInternalRawFixes.getWindWithConfidence(timePoint); - ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE.createWindAverager(weigher); - List>> windFixesToAverage = new ArrayList>>(); - if (fixAtTimePoint == null) { - final WindWithConfidence beforeAt = getWindWithConfidenceBefore(timePoint); - final WindWithConfidence afterAt = getWindWithConfidenceAfter(timePoint); - if (beforeAt != null) { - windFixesToAverage.add(createWindWithConfidenceForPositionAndTime(beforeAt)); - } - if (afterAt != null) { - windFixesToAverage.add(createWindWithConfidenceForPositionAndTime(afterAt)); - } - } else { - windFixesToAverage.add(createWindWithConfidenceForPositionAndTime(fixAtTimePoint)); - } - result = windAverager.getAverage(windFixesToAverage, relativeTo); - return result; - } finally { - unlockAfterRead(); - } - } - - private WindWithConfidenceImpl> createWindWithConfidenceForPositionAndTime( - final WindWithConfidence preResult) { - return new WindWithConfidenceImpl>(preResult.getObject(), - preResult.getConfidence(), new Pair<>(preResult.getObject().getPosition(), preResult.getObject().getTimePoint()), - preResult.useSpeed()); - } - - private WindWithConfidence getWindWithConfidenceBefore(TimePoint timePoint) { - final TimePoint leftBump = getTrackedRace().getTimePointOfOldestEvent(); - final WindWithConfidence result; - if (leftBump == null) { - result = null; - } else { - final TimePoint cutoffForRecalculation = timePoint.minus(getMillisecondsOverWhichToAverageWind()/2); - TimePoint t = virtualInternalRawFixes.lowerToResolution(timePoint); - WindWithConfidence candidate = null; - while (candidate == null && !t.before(leftBump)) { - if (t.before(cutoffForRecalculation)) { - // pick the next best candidate from the cache - candidate = cache.floor(new WindWithConfidenceImpl(virtualInternalRawFixes.createDummyWindFix(t), - 0.0, /* relativeTo */ null, /* useSpeed */ false)); - break; - } else { - candidate = virtualInternalRawFixes.getWindWithConfidence(t); - t = virtualInternalRawFixes.lowerToResolution(t); - } - } - result = candidate; - } - return result; - } - - private WindWithConfidence getWindWithConfidenceAfter(TimePoint timePoint) { - final TimePoint rightBump = getTrackedRace().getTimePointOfNewestEvent(); - final WindWithConfidence result; - if (rightBump == null) { - result = null; - } else { - final TimePoint cutoffForRecalculation = timePoint.plus(getMillisecondsOverWhichToAverageWind()/2); - TimePoint t = virtualInternalRawFixes.higherToResolution(timePoint); - WindWithConfidence candidate = null; - while (candidate == null && !t.after(rightBump)) { - if (t.after(cutoffForRecalculation)) { - // pick the next best candidate from the cache - candidate = cache.ceiling(new WindWithConfidenceImpl(virtualInternalRawFixes.createDummyWindFix(t), - 0.0, /* relativeTo */ null, /* useSpeed */ false)); - break; - } else { - candidate = virtualInternalRawFixes.getWindWithConfidence(t); - t = virtualInternalRawFixes.higherToResolution(t); - } - } - result = candidate; - } - return result; - } - @Override public String toString() { lockForRead(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 27aa40e5110..e217730e1d7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -183,17 +183,6 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName()); - /** - * The resolution at which {@link #getWind(Position, TimePoint)} and {@link #getWind(Position, TimePoint, Set)} and - * {@link #getWindWithConfidence(Position, TimePoint)} and {@link #getWindWithConfidence(Position, TimePoint, Set)} - * traverse an interval of length {@link #getMillisecondsOverWhichToAverageWind()}ms around a given time point - * in order to compute a smoothened average for a wind vector. This is then also the basis for the so-called - * "combined" wind and the "leg middle" wind (a specialization of the "combined" wind computed for a different - * position). - */ - private static final Duration WIND_TRACK_RESOLUTION_FOR_SMOOTHENING = Duration.ONE_SECOND; - - // TODO make this variable private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500; public static final Duration TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = Duration.ONE_MINUTE.times(4); // let wind start four minutes before race @@ -2026,21 +2015,17 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } @Override - public WindWithConfidence> getWindWithConfidence(Position p, TimePoint at, Set windSourcesToExclude) { - final Weigher> timeWeigher = (pair1, pair2)-> - ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(WindTrack.WIND_HALF_CONFIDENCE_TIME_MILLIS).getConfidence(pair1.getB(), pair2.getB()); - // at a 1s interval (or whatever WIND_TRACK_RESOLUTION_FOR_SMOOTHENING says) go to earlier and later time points starting - // from "at" and collect the wind fixes averaged over the wind sources to use, then average them based on time point only - ConfidenceBasedWindAverager> averager = ConfidenceFactory.INSTANCE.createWindAverager(timeWeigher); - final Set>> fixes = new HashSet<>(); - final TimePoint end = at.plus(getMillisecondsOverWhichToAverageWind()/2); - for (TimePoint t=at.minus(getMillisecondsOverWhichToAverageWind()/2); !t.after(end); t=t.plus(WIND_TRACK_RESOLUTION_FOR_SMOOTHENING)) { - final WindWithConfidence> windWithConfidence = shortTimeWindCache.getWindWithConfidence(p, t, windSourcesToExclude); - if (windWithConfidence != null) { - fixes.add(windWithConfidence); - } - } - return averager.getAverage(fixes, new Pair<>(p, at)); + public WindWithConfidence> getWindWithConfidence(Position position, TimePoint at, + Set windSourcesToExclude) { + final WindWithConfidence> windWithConfidence = shortTimeWindCache + .getWindWithConfidence(position, roundToDuration(at, Duration.ONE_SECOND), windSourcesToExclude); + return windWithConfidence; + } + + private TimePoint roundToDuration(TimePoint t, Duration roundTo) { + final long roundToMillis = roundTo.asMillis(); + final long half = roundToMillis/2; + return new MillisecondsTimePoint((t.asMillis()+half) / roundToMillis * roundToMillis); } public WindWithConfidence> getWindWithConfidenceUncached(Position p, TimePoint at, diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index 21480439e10..deb8eb9b22f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -15,6 +15,7 @@ import com.sap.sailing.domain.common.CourseChange; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.confidence.Weigher; import com.sap.sailing.domain.common.confidence.impl.PositionAndTimePointWeigher; import com.sap.sailing.domain.common.tracking.impl.CompactPositionHelper; import com.sap.sailing.domain.common.tracking.impl.CompactionNotPossibleException; @@ -55,6 +56,8 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { private final boolean useSpeed; + protected final Weigher> weigher; + /** * Listeners won't be serialized. */ @@ -108,6 +111,7 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { listeners = new HashSet(); this.useSpeed = useSpeed; this.losslessCompaction = losslessCompaction; + this.weigher = createPositionAndTimePointWeigher(millisecondsOverWhichToAverage); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { @@ -262,10 +266,7 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { try { List>> windFixesToAverage = new ArrayList>>(); // don't measure speed with separate confidence; return confidence obtained from averaging bearings - ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE - .createWindAverager(new PositionAndTimePointWeigher( - /* halfConfidenceAfterMilliseconds */getMillisecondsOverWhichToAverageWind() / 10, - WIND_HALF_CONFIDENCE_DISTANCE)); + ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE.createWindAverager(weigher); DummyWind atTimed = new DummyWind(at); Util.Pair relativeTo = new Util.Pair(p, at); NavigableSet beforeSet = getInternalFixes().headSet(atTimed, /* inclusive */false); @@ -343,6 +344,12 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } } + private PositionAndTimePointWeigher createPositionAndTimePointWeigher(long millisecondsOverWhichToAverage) { + return new PositionAndTimePointWeigher( + /* halfConfidenceAfterMilliseconds */ millisecondsOverWhichToAverage / 2, + WIND_HALF_CONFIDENCE_DISTANCE); + } + private WindWithConfidenceImpl> createWindWithConfidence(Wind wind) { return new WindWithConfidenceImpl>(wind, getConfidenceOfInternalWindFixUnsynchronized(wind), new Util.Pair(wind.getPosition(), wind diff --git a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/IncrementalMstHmmWindEstimationForTrackedRace.java b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/IncrementalMstHmmWindEstimationForTrackedRace.java index 01958a761c5..ae957092612 100644 --- a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/IncrementalMstHmmWindEstimationForTrackedRace.java +++ b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/IncrementalMstHmmWindEstimationForTrackedRace.java @@ -54,7 +54,6 @@ import com.sap.sse.common.Util.Pair; public class IncrementalMstHmmWindEstimationForTrackedRace implements IncrementalWindEstimation { private static final double WIND_COURSE_TOLERANCE_IN_DEGREES_TO_IGNORE_FOR_REUSE = 1.0; - private static final double DEFAULT_BASE_CONFIDENCE = 0.01; private final IncrementalMstManeuverGraphGenerator mstManeuverGraphGenerator; private final MstBestPathsCalculator bestPathsCalculator; private final WindTrackCalculator windTrackCalculator; @@ -69,7 +68,7 @@ public class IncrementalMstHmmWindEstimationForTrackedRace implements Incrementa ManeuverClassifiersCache maneuverClassifiersCache, GaussianBasedTwdTransitionDistributionCache gaussianBasedTwdTransitionDistributionCache) { this.estimatedWindTrack = new WindTrackWithConfidenceForEachWindFixImpl(millisecondsOverWhichToAverage, - DEFAULT_BASE_CONFIDENCE, + WindSourceType.MANEUVER_BASED_ESTIMATION.getBaseConfidence(), WindSourceType.MANEUVER_BASED_ESTIMATION.useSpeed() && polarDataService != null, IncrementalMstHmmWindEstimationForTrackedRace.class.getSimpleName()+" "+ trackedRace.getRaceIdentifier(), false, windTrackWithConfidences); From a28d27a2326c145dfbe68059da4e798136987453 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jun 2021 18:53:09 +0200 Subject: [PATCH 08/37] bug5576: cope better with time gaps on tracks by going 15s each direction (at least one fix) and not more --- .../domain/tracking/impl/WindTrackImpl.java | 74 ++++++++----------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index deb8eb9b22f..da60dc8d2cb 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -230,32 +230,20 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { /** * This method implements the functionality of the {@link #getAveragedWind(Position, TimePoint)} interface method. - * It does so by collecting (smoothened, outliers removed) wind fixes around the at time point up to an - * interval length as specified by {@link #getMillisecondsOverWhichToAverageWind()}. If available, at least the fix - * time-wise closest before and the fix time-wise closest after {@code at} will be picked up, which may lead to an - * overall interval length that exceeds {@link #getMillisecondsOverWhichToAverageWind()}. - *

- * - * Collecting the fixes around {@code at} tries to work symmetrically. The interval is counted from the earliest fix - * used to the latest fix used, and always including {@code at}. After adding the latest fix before and the earliest - * fix after {@code at} (if they exist) to the result, while the interval that has to include {@code at} does not - * yet exceed {@link #getMillisecondsOverWhichToAverageWind()}, the next fix that is closest to {@code at} and that - * is not yet part of the result is added unless it would extend the interval beyond - * {@link #getMillisecondsOverWhichToAverageWind()} in which case the iteration ends. - *

- * - * While this procedure does not guarantee an equal number of fixes on both sides of {@code at}, it works well for - * producing fixes that are aligned closely around {@code at} while also guaranteeing that fixes from both sides of - * {@code at} are considered when available. + * It does so by collecting (smoothened, outliers removed) wind fixes around the at time point up to + * half interval length specified by {@link #getMillisecondsOverWhichToAverageWind()} in each direction. If + * available, at least the fix time-wise closest before and the fix time-wise closest after {@code at} will be + * picked up, which may lead to an overall interval length that exceeds + * {@link #getMillisecondsOverWhichToAverageWind()}. *

* * If the track is empty, {@code null} is returned. Otherwise, the wind fixes in the interval constructed are * averaged using a {@link ConfidenceBasedWindAverager}. *

* - * Not being synchronized, it does not obtain this object's monitor. Subclasses may use this - * carefully if they can guarantee there are no concurrency issues with the internal fixes while iterating over the - * result of {@link #getInternalFixes()}. + * Not being synchronized, it does not obtain this object's monitor. Subclasses may use this carefully + * if they can guarantee there are no concurrency issues with the internal fixes while iterating over the result of + * {@link #getInternalFixes()}. * * @param p * if null, the averaged position of the original wind fixes is returned; otherwise, @@ -305,32 +293,28 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { afterWind = null; newAfterDistanceToAt = afterDistanceToAt; } - boolean pickBefore; - // Invariant: beforeWind and afterWind each represent the next element in the respective direction that has not yet been consumed, - // beforeDistanceToAt and afterDistanceToAt refer to the fixes consumed to far, newBeforeDistanceToAt and newAfterDistanceToAt - // refer to the yet unconsumed next fix in the corresponding direction. - while ((pickBefore=(beforeWind != null && (afterWind == null || newBeforeDistanceToAt <= newAfterDistanceToAt) && newBeforeDistanceToAt + afterDistanceToAt <= getMillisecondsOverWhichToAverageWind())) - || (afterWind != null && beforeDistanceToAt + newAfterDistanceToAt <= getMillisecondsOverWhichToAverageWind())) { - if (pickBefore) { - windFixesToAverage.add(createWindWithConfidence(beforeWind)); - beforeDistanceToAt = newBeforeDistanceToAt; - if (beforeIter.hasNext()) { - beforeWind = beforeIter.next(); - newBeforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); - } else { - beforeWind = null; - newBeforeDistanceToAt = beforeDistanceToAt; - } + // go up to half the interval backward in time: + while (beforeWind != null && newBeforeDistanceToAt <= getMillisecondsOverWhichToAverageWind()/2) { + windFixesToAverage.add(createWindWithConfidence(beforeWind)); + beforeDistanceToAt = newBeforeDistanceToAt; + if (beforeIter.hasNext()) { + beforeWind = beforeIter.next(); + newBeforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); } else { - windFixesToAverage.add(createWindWithConfidence(afterWind)); - afterDistanceToAt = newAfterDistanceToAt; - if (afterIter.hasNext()) { - afterWind = afterIter.next(); - newAfterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); - } else { - afterWind = null; - newAfterDistanceToAt = afterDistanceToAt; - } + beforeWind = null; + newBeforeDistanceToAt = beforeDistanceToAt; + } + } + // go up to half the interval forward in time: + while (afterWind != null && newAfterDistanceToAt <= getMillisecondsOverWhichToAverageWind()/2) { + windFixesToAverage.add(createWindWithConfidence(afterWind)); + afterDistanceToAt = newAfterDistanceToAt; + if (afterIter.hasNext()) { + afterWind = afterIter.next(); + newAfterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); + } else { + afterWind = null; + newAfterDistanceToAt = afterDistanceToAt; } } if (windFixesToAverage.isEmpty()) { From 1e115c79e53cde95d1b21428aeee3a4f0010d4fa Mon Sep 17 00:00:00 2001 From: Alessandro Stoltenberg Date: Tue, 15 Jun 2021 20:39:51 +0200 Subject: [PATCH 09/37] Olympic Setup: Documented s3 bucket for backups. --- wiki/info/landscape/olympic-setup.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wiki/info/landscape/olympic-setup.md b/wiki/info/landscape/olympic-setup.md index b48cbd498c7..35dda0f6d60 100755 --- a/wiki/info/landscape/olympic-setup.md +++ b/wiki/info/landscape/olympic-setup.md @@ -301,6 +301,8 @@ borgbackup is used to backup the ``/`` folder of both laptops towards the other The backup from sap-p1-1 to sap-p1-2 runs at 01:00 each day, and the backup from sap-p1-2 to sap-p1-1 runs at 02:00 each day. Details about the configuration can be found in ``/root/borg-backup.sh`` on either machine. Log files for the backup run are in ``/var/log/backup.log``. Crontab file is in ``/root``. +Both ``/backup`` folders have been mirrored to a S3 bucket called ``backup-sap-p1`` on June 14th. + ### Monitoring and e-Mail Alerting To be able to use ``sendmail`` to send notifications via email it needs to be installed and configured to use the AWS SES as smtp relay: From 4c4f0525a990c9575de34eea60af2a1056cbe654 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Jun 2021 22:07:12 +0200 Subject: [PATCH 10/37] bug5576: refactored WindTrackImpl.getAveragedWindUnsynchronized to remove code redundancies --- .../domain/tracking/impl/WindTrackImpl.java | 96 ++++++++----------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index da60dc8d2cb..b4651ce0bcd 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.tracking.impl; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -257,66 +258,14 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE.createWindAverager(weigher); DummyWind atTimed = new DummyWind(at); Util.Pair relativeTo = new Util.Pair(p, at); + // pick one left if possible and extend to half averaging interval length NavigableSet beforeSet = getInternalFixes().headSet(atTimed, /* inclusive */false); + final Iterator beforeIter = beforeSet.descendingIterator(); + collectFixesInOneDirection(at, beforeIter, windFixesToAverage); + // pick one right if possible and extend to half averaging interval length NavigableSet afterSet = getInternalFixes().tailSet(atTimed, /* inclusive */true); - Iterator beforeIter = beforeSet.descendingIterator(); - Iterator afterIter = afterSet.iterator(); - long beforeDistanceToAt = 0; - long afterDistanceToAt = 0; - Wind beforeWind = null; - Wind afterWind = null; - // pick one left if possible - if (beforeIter.hasNext()) { - beforeWind = beforeIter.next(); - beforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); - windFixesToAverage.add(createWindWithConfidence(beforeWind)); - } - // pick one right if possible - if (afterIter.hasNext()) { - afterWind = afterIter.next(); - afterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); - windFixesToAverage.add(createWindWithConfidence(afterWind)); - } - long newBeforeDistanceToAt; - long newAfterDistanceToAt; - if (beforeIter.hasNext()) { - beforeWind = beforeIter.next(); - newBeforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); - } else { - beforeWind = null; - newBeforeDistanceToAt = beforeDistanceToAt; - } - if (afterIter.hasNext()) { - afterWind = afterIter.next(); - newAfterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); - } else { - afterWind = null; - newAfterDistanceToAt = afterDistanceToAt; - } - // go up to half the interval backward in time: - while (beforeWind != null && newBeforeDistanceToAt <= getMillisecondsOverWhichToAverageWind()/2) { - windFixesToAverage.add(createWindWithConfidence(beforeWind)); - beforeDistanceToAt = newBeforeDistanceToAt; - if (beforeIter.hasNext()) { - beforeWind = beforeIter.next(); - newBeforeDistanceToAt = at.asMillis() - beforeWind.getTimePoint().asMillis(); - } else { - beforeWind = null; - newBeforeDistanceToAt = beforeDistanceToAt; - } - } - // go up to half the interval forward in time: - while (afterWind != null && newAfterDistanceToAt <= getMillisecondsOverWhichToAverageWind()/2) { - windFixesToAverage.add(createWindWithConfidence(afterWind)); - afterDistanceToAt = newAfterDistanceToAt; - if (afterIter.hasNext()) { - afterWind = afterIter.next(); - newAfterDistanceToAt = afterWind.getTimePoint().asMillis() - at.asMillis(); - } else { - afterWind = null; - newAfterDistanceToAt = afterDistanceToAt; - } - } + final Iterator afterIter = afterSet.iterator(); + collectFixesInOneDirection(at, afterIter, windFixesToAverage); if (windFixesToAverage.isEmpty()) { return null; } else { @@ -328,6 +277,37 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } } + private void collectFixesInOneDirection(final TimePoint at, final Iterator fixIter, Collection>> windFixesToAverage) { + long distanceToAt = 0; + Wind nextWindFix = null; + if (fixIter.hasNext()) { + nextWindFix = fixIter.next(); + distanceToAt = Math.abs(at.asMillis() - nextWindFix.getTimePoint().asMillis()); + windFixesToAverage.add(createWindWithConfidence(nextWindFix)); + } + // and extend to at most half the averaging interval + long newDistanceToAt; + if (fixIter.hasNext()) { + nextWindFix = fixIter.next(); + newDistanceToAt = Math.abs(at.asMillis() - nextWindFix.getTimePoint().asMillis()); + } else { + nextWindFix = null; + newDistanceToAt = distanceToAt; + } + // go up to half the interval: + while (nextWindFix != null && newDistanceToAt <= getMillisecondsOverWhichToAverageWind()/2) { + windFixesToAverage.add(createWindWithConfidence(nextWindFix)); + distanceToAt = newDistanceToAt; + if (fixIter.hasNext()) { + nextWindFix = fixIter.next(); + newDistanceToAt = Math.abs(at.asMillis() - nextWindFix.getTimePoint().asMillis()); + } else { + nextWindFix = null; + newDistanceToAt = distanceToAt; + } + } + } + private PositionAndTimePointWeigher createPositionAndTimePointWeigher(long millisecondsOverWhichToAverage) { return new PositionAndTimePointWeigher( /* halfConfidenceAfterMilliseconds */ millisecondsOverWhichToAverage / 2, From a586f9695b59d1a11a34b551585e4da1d9d1e928 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jun 2021 00:33:52 +0200 Subject: [PATCH 11/37] bug5576: comment #25: grabbing 15s starting at each side's first fix to reduce gap border significance --- .../impl/PositionAndTimePointWeigher.java | 6 +-- .../sailing/domain/tracking/WindTrack.java | 2 +- .../domain/tracking/impl/TrackedRaceImpl.java | 2 +- .../domain/tracking/impl/WindTrackImpl.java | 44 ++++++++++--------- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java index c9736434ef7..ac155861d72 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java @@ -4,9 +4,9 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.confidence.ConfidenceFactory; import com.sap.sailing.domain.common.confidence.Weigher; import com.sap.sse.common.Distance; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsDurationImpl; /** * A weigher that uses a {@link Position} and a {@link TimePoint} to compute a confidence based on @@ -26,9 +26,9 @@ public class PositionAndTimePointWeigher implements Weigher distanceWeigher; private final boolean usePosition; - public PositionAndTimePointWeigher(long halfConfidenceAfterMilliseconds, Distance halfConfidenceDistance) { + public PositionAndTimePointWeigher(Duration halfConfidenceAfter, Distance halfConfidenceDistance) { timeWeigher = ConfidenceFactory.INSTANCE.createStandardDistributionTimeDifferenceWeigher( - /* use as standard deviation */ new MillisecondsDurationImpl(halfConfidenceAfterMilliseconds)); + /* use as standard deviation */ halfConfidenceAfter); distanceWeigher = ConfidenceFactory.INSTANCE.createHyperbolicDistanceWeigher(halfConfidenceDistance); this.usePosition = Boolean.valueOf(System.getProperty(USE_POSITION_SYSTEM_PROPERTY_NAME, "true")); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java index b57dc1f0be9..d6b333145b7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java @@ -12,7 +12,7 @@ public interface WindTrack extends DynamicTrack { static final long DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND = 30000; public static final Distance WIND_HALF_CONFIDENCE_DISTANCE = new MeterDistance(100); - public static final long WIND_HALF_CONFIDENCE_TIME_MILLIS = 10000l; + public static final Duration WIND_HALF_CONFIDENCE_DURATION = Duration.ONE_MINUTE; /** * Estimates a wind force and direction based on tracked wind data.

diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index e217730e1d7..c76edea94cd 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -2032,7 +2032,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl Iterable windSourcesToExclude) { boolean canUseSpeedOfAtLeastOneWindSource = false; Weigher> weigher = new PositionAndTimePointWeigher( - /* halfConfidenceAfterMilliseconds */WindTrack.WIND_HALF_CONFIDENCE_TIME_MILLIS, WindTrack.WIND_HALF_CONFIDENCE_DISTANCE); + /* halfConfidenceAfterMilliseconds */WindTrack.WIND_HALF_CONFIDENCE_DURATION, WindTrack.WIND_HALF_CONFIDENCE_DISTANCE); ConfidenceBasedWindAverager> averager = ConfidenceFactory.INSTANCE .createWindAverager(weigher); List>> windFixesWithConfidences = new ArrayList>>(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index b4651ce0bcd..1256c17d96f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -112,7 +112,7 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { listeners = new HashSet(); this.useSpeed = useSpeed; this.losslessCompaction = losslessCompaction; - this.weigher = createPositionAndTimePointWeigher(millisecondsOverWhichToAverage); + this.weigher = createPositionAndTimePointWeigher(); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { @@ -231,10 +231,10 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { /** * This method implements the functionality of the {@link #getAveragedWind(Position, TimePoint)} interface method. - * It does so by collecting (smoothened, outliers removed) wind fixes around the at time point up to - * half interval length specified by {@link #getMillisecondsOverWhichToAverageWind()} in each direction. If - * available, at least the fix time-wise closest before and the fix time-wise closest after {@code at} will be - * picked up, which may lead to an overall interval length that exceeds + * It does so by collecting wind fixes around the at time point up to half interval length specified by + * {@link #getMillisecondsOverWhichToAverageWind()} in each direction, starting to count at the first fix found in + * each direction. If available, at least the fix time-wise closest before and the fix time-wise closest after + * {@code at} will be picked up, which may lead to an overall interval length that exceeds * {@link #getMillisecondsOverWhichToAverageWind()}. *

* @@ -261,11 +261,11 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { // pick one left if possible and extend to half averaging interval length NavigableSet beforeSet = getInternalFixes().headSet(atTimed, /* inclusive */false); final Iterator beforeIter = beforeSet.descendingIterator(); - collectFixesInOneDirection(at, beforeIter, windFixesToAverage); + collectFixesInOneDirection(beforeIter, windFixesToAverage); // pick one right if possible and extend to half averaging interval length NavigableSet afterSet = getInternalFixes().tailSet(atTimed, /* inclusive */true); final Iterator afterIter = afterSet.iterator(); - collectFixesInOneDirection(at, afterIter, windFixesToAverage); + collectFixesInOneDirection(afterIter, windFixesToAverage); if (windFixesToAverage.isEmpty()) { return null; } else { @@ -277,41 +277,43 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } } - private void collectFixesInOneDirection(final TimePoint at, final Iterator fixIter, Collection>> windFixesToAverage) { - long distanceToAt = 0; + private void collectFixesInOneDirection(final Iterator fixIter, Collection>> windFixesToAverage) { + long distanceToFirst = 0; Wind nextWindFix = null; + final TimePoint firstTimePointInDirection; if (fixIter.hasNext()) { nextWindFix = fixIter.next(); - distanceToAt = Math.abs(at.asMillis() - nextWindFix.getTimePoint().asMillis()); + firstTimePointInDirection = nextWindFix.getTimePoint(); + distanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getTimePoint().asMillis()); windFixesToAverage.add(createWindWithConfidence(nextWindFix)); + } else { + firstTimePointInDirection = null; } // and extend to at most half the averaging interval - long newDistanceToAt; + long newDistanceToFirst; if (fixIter.hasNext()) { nextWindFix = fixIter.next(); - newDistanceToAt = Math.abs(at.asMillis() - nextWindFix.getTimePoint().asMillis()); + newDistanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getTimePoint().asMillis()); } else { nextWindFix = null; - newDistanceToAt = distanceToAt; + newDistanceToFirst = distanceToFirst; } // go up to half the interval: - while (nextWindFix != null && newDistanceToAt <= getMillisecondsOverWhichToAverageWind()/2) { + while (nextWindFix != null && newDistanceToFirst <= getMillisecondsOverWhichToAverageWind()/2) { windFixesToAverage.add(createWindWithConfidence(nextWindFix)); - distanceToAt = newDistanceToAt; + distanceToFirst = newDistanceToFirst; if (fixIter.hasNext()) { nextWindFix = fixIter.next(); - newDistanceToAt = Math.abs(at.asMillis() - nextWindFix.getTimePoint().asMillis()); + newDistanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getTimePoint().asMillis()); } else { nextWindFix = null; - newDistanceToAt = distanceToAt; + newDistanceToFirst = distanceToFirst; } } } - private PositionAndTimePointWeigher createPositionAndTimePointWeigher(long millisecondsOverWhichToAverage) { - return new PositionAndTimePointWeigher( - /* halfConfidenceAfterMilliseconds */ millisecondsOverWhichToAverage / 2, - WIND_HALF_CONFIDENCE_DISTANCE); + private PositionAndTimePointWeigher createPositionAndTimePointWeigher() { + return new PositionAndTimePointWeigher(WIND_HALF_CONFIDENCE_DURATION, WIND_HALF_CONFIDENCE_DISTANCE); } private WindWithConfidenceImpl> createWindWithConfidence(Wind wind) { From 2712521f8009dcc7187ef4400c2e2c256f05a21f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jun 2021 00:46:49 +0200 Subject: [PATCH 12/37] bug5576: removed confidence check before showing a measured wind source on the map; this helps WindFinder as well as slow EXPEDITION / WindBot sources --- .../sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index 84152405056..47d1f32e02f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -1121,11 +1121,7 @@ public class RaceMap extends AbstractCompositeComponent impleme switch (windSource.getType()) { case EXPEDITION: case WINDFINDER: - // we filter out measured wind sources with very low confidence - if (windTrackInfoDTO.minWindConfidence > 0.0001) { - windSourcesToShow.add(new com.sap.sse.common.Util.Pair(windSource, - windTrackInfoDTO)); - } + windSourcesToShow.add(new com.sap.sse.common.Util.Pair(windSource, windTrackInfoDTO)); break; case COMBINED: showCombinedWindOnMap(windSource, windTrackInfoDTO); From fcb5e82d348ccc3baa9f24ec98ab55d286383ac7 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jun 2021 09:20:14 +0200 Subject: [PATCH 13/37] bug5576: fixed compilation error in WindTest --- .../src/com/sap/sailing/domain/test/WindTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index e599d89aec4..f6f531db760 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -370,7 +370,7 @@ public class WindTest { @Test public void testWindAveragingBasedOnPosition() { Weigher> timeWeigherThatPretendsToAlsoWeighPositions = new PositionAndTimePointWeigher( - /* halfConfidenceAfterMilliseconds */10000l, new MeterDistance(1000)); + /* standard deviation */ Duration.ONE_SECOND.times(10), new MeterDistance(1000)); ConfidenceBasedWindAverager> averager = ConfidenceFactory.INSTANCE .createWindAverager(timeWeigherThatPretendsToAlsoWeighPositions); TimePoint now = MillisecondsTimePoint.now(); From 845ed831ec3558daa65797e1d70a2003e91609d1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jun 2021 15:47:34 +0200 Subject: [PATCH 14/37] bug5576: improved test case by setting wind at a plausible date (race start time) instead of now() --- .../test/ManeuverDetectionOnKielerWoche505Race2DataTest.java | 2 +- .../com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverDetectionOnKielerWoche505Race2DataTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverDetectionOnKielerWoche505Race2DataTest.java index 5deb440b068..92f0632c199 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverDetectionOnKielerWoche505Race2DataTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverDetectionOnKielerWoche505Race2DataTest.java @@ -55,7 +55,7 @@ public class ManeuverDetectionOnKielerWoche505Race2DataTest extends AbstractMane /* liveUri */ null, /* storedUri */ storedUri, new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS }); OnlineTracTracBasedTest.fixApproximateMarkPositionsForWindReadOut(getTrackedRace(), new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime())); - getTrackedRace().recordWind(new WindImpl(/* position */ null, MillisecondsTimePoint.now(), + getTrackedRace().recordWind(new WindImpl(/* position */ null, getTrackedRace().getStartOfRace(), new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(60))), new WindSourceImpl(WindSourceType.WEB)); logger.info("Waiting for things to settle in, such as wind updates..."); Thread.sleep(2000); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index c76edea94cd..4f2d3b2f110 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -2551,7 +2551,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl // if bearings was set to null this indicates there was an exception; no need for further calculations, // return null if (bearings != null && leg != null) { - TrackedLeg trackedLeg = getTrackedLeg(leg.getLeg()); + TrackedLeg trackedLeg = leg.getTrackedLeg(); LegType legType; try { legType = legTypesCache.get(trackedLeg); From 5b749bd46ea23cb25a9b3b209d398477023f1384 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Wed, 16 Jun 2021 18:22:56 +0200 Subject: [PATCH 15/37] Ensuring empty cache triggers loadSpots for WindFinder --- .../windfinderadapter/impl/ReviewedSpotsCollectionImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java index c26d48e16a2..1757e423c6c 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java @@ -92,7 +92,7 @@ public class ReviewedSpotsCollectionImpl implements ReviewedSpotsCollection { @Override public Iterable getSpots(boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { final Iterable result; - if (cached) { + if (cached && !spotsByIdCache.get().isEmpty()) { result = new HashSet<>(spotsByIdCache.get().values()); } else { result = loadSpots(); From 7b45ec066f511ec0c686ededba57eca5d7cf1ae4 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Wed, 16 Jun 2021 18:24:21 +0200 Subject: [PATCH 16/37] Implemented back off handling for consecutively failed calls. --- .../impl/ReviewedSpotsCollectionImpl.java | 42 ++++++++++--------- .../sap/sse/common/util/BackoffTracker.java | 40 ++++++++++++++++++ 2 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 java/com.sap.sse.common/src/com/sap/sse/common/util/BackoffTracker.java diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java index 1757e423c6c..c2d2a6b8098 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; +import java.util.Collections; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -20,6 +21,7 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sse.common.util.BackoffTracker; import com.sap.sse.util.HttpUrlConnectionHelper; import com.sap.sse.util.ThreadPoolUtil; @@ -42,18 +44,17 @@ public class ReviewedSpotsCollectionImpl implements ReviewedSpotsCollection { * map initialized with the result of calling {@link #loadSpots()}. */ private Future> spotsByIdCache; + + private BackoffTracker backoffTracker; public ReviewedSpotsCollectionImpl(String id) { this.id = id; this.parser = new WindFinderReportParser(); - this.spotsByIdCache = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().schedule(()->{ + this.spotsByIdCache = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().schedule(() -> { + backoffTracker = new BackoffTracker(TimeUnit.SECONDS.toMillis(5), 2); final ConcurrentMap result = new ConcurrentHashMap<>(); - try { - for (final Spot spot : loadSpots()) { - result.put(spot.getId(), spot); - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Problem loading spots for spot collection "+id, e); + for (final Spot spot : loadSpots()) { + result.put(spot.getId(), spot); } return result; }, /* delay */ 0, TimeUnit.MILLISECONDS); @@ -103,19 +104,22 @@ public class ReviewedSpotsCollectionImpl implements ReviewedSpotsCollection { return result; } - private Iterable loadSpots() throws IOException, ParseException, MalformedURLException { - final Iterable result; - final InputStreamReader in = new InputStreamReader( - (InputStream) HttpUrlConnectionHelper.redirectConnection( - new URL(Activator.BASE_URL_FOR_JSON_DOCUMENTS+"/"+getId()+SPOT_LIST_DOCUMENT_SUFFIX)) - .getContent()); - try { - JSONArray spotsAsJson = (JSONArray) new JSONParser().parse(in); - result = parser.parseSpots(spotsAsJson, this); - return result; - } finally { - in.close(); + private Iterable loadSpots() { + Iterable result = Collections.emptySet(); + if (!backoffTracker.backOff()) { + try (InputStreamReader in = new InputStreamReader((InputStream) HttpUrlConnectionHelper + .redirectConnection( + new URL(Activator.BASE_URL_FOR_JSON_DOCUMENTS + "/" + getId() + SPOT_LIST_DOCUMENT_SUFFIX)) + .getContent())) { + JSONArray spotsAsJson = (JSONArray) new JSONParser().parse(in); + result = parser.parseSpots(spotsAsJson, this); + backoffTracker.clear(); + } catch (Exception e) { + logger.log(Level.SEVERE, "Problem loading spots for spot collection " + id, e); + backoffTracker.logFailure(); + } } + return result; } @Override diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/util/BackoffTracker.java b/java/com.sap.sse.common/src/com/sap/sse/common/util/BackoffTracker.java new file mode 100644 index 00000000000..a7e2784cdd4 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/util/BackoffTracker.java @@ -0,0 +1,40 @@ +package com.sap.sse.common.util; + +import java.util.concurrent.TimeUnit; + +public class BackoffTracker { + private Long backOffUntil; + private final int factor; + private Long currentTimeoutInMillis; + private final Long initialTimeoutInMillis; + private static final Long maxTimeout = TimeUnit.MINUTES.toMillis(5); + + /** + * A tracker to handle continous failures of processes. Before every process execution call {@link #backOff()} to query whether there is a timeout. + * Every failure should call {@link #logFailure()}, which will increase the time until {@link #backOff()} returns false again. + * @param initialTimeoutInMillis + * @param backoffMultiplier: The factor by which the timeout will be multiplied on consecutive failures + */ + public BackoffTracker(Long initialTimeoutInMillis, int backoffMultiplier) { + this.initialTimeoutInMillis = initialTimeoutInMillis; + this.factor = backoffMultiplier; + } + + public void logFailure() { + if(currentTimeoutInMillis == null) { + currentTimeoutInMillis = initialTimeoutInMillis; + } + final Long newTimeOut = currentTimeoutInMillis * factor; + currentTimeoutInMillis = newTimeOut >= maxTimeout ? maxTimeout : newTimeOut; + backOffUntil = System.currentTimeMillis() + currentTimeoutInMillis; + } + + public boolean backOff() { + return backOffUntil == null ? false : backOffUntil > System.currentTimeMillis(); + } + + public void clear() { + currentTimeoutInMillis = null; + backOffUntil = null; + } +} From 3804f2a8dcc9780c1d779cf29df98f9da770950b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jun 2021 18:58:31 +0200 Subject: [PATCH 17/37] bug5576: limit the head/tail sets for estimation track when picking fixes to average --- .../domain/test/AbstractTracTracLiveTest.java | 8 ++++---- .../sap/sailing/domain/tracking/WindTrack.java | 7 ++++++- .../TrackBasedEstimationWindTrackImpl.java | 18 ++++++++++++++++++ .../domain/tracking/impl/WindTrackImpl.java | 12 ++++++++++-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java index 3f8364d6106..9cc4cb3ff9e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java @@ -60,10 +60,10 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest { /** * Making this a method rule allows subclasses to adjust the timeout if required */ - @Rule - public TestRule getTimeoutRule() { - return Timeout.millis(3 * 60 * 1000); - } +// @Rule +// public TestRule getTimeoutRule() { +// return Timeout.millis(3 * 60 * 1000); +// } protected AbstractTracTracLiveTest() throws URISyntaxException, MalformedURLException { receivers = new HashSet(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java index d6b333145b7..5274f56529a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java @@ -12,7 +12,12 @@ public interface WindTrack extends DynamicTrack { static final long DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND = 30000; public static final Distance WIND_HALF_CONFIDENCE_DISTANCE = new MeterDistance(100); - public static final Duration WIND_HALF_CONFIDENCE_DURATION = Duration.ONE_MINUTE; + + /** + * The following is used as a "standard deviation" in a Gaussian normal distribution; in order + * to still get positive confidences, a rather large duration of 30min is chosen here. + */ + public static final Duration WIND_HALF_CONFIDENCE_DURATION = Duration.ONE_MINUTE.times(30); /** * Estimates a wind force and direction based on tracked wind data.

diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index 690d032e9a0..b7d29c61e76 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -796,6 +796,24 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { } } + + /** + * Limits the head set to a length of 2*{@link #getMillisecondsOverWhichToAverageWind()} to avoid + * an expensive search for valid estimation fixes in a huge or maybe even open-ended time range. + */ + protected NavigableSet getInternalFixesLimitedHeadSet(Wind endingAt) { + final Wind startingAt = new DummyWind(endingAt.getTimePoint().minus(2*getMillisecondsOverWhichToAverageWind())); + return getInternalFixes().subSet(startingAt, /* inclusive */ true, endingAt, /* inclusive */ false); + } + + /** + * Limits the tail set to a length of 2*{@link #getMillisecondsOverWhichToAverageWind()} to avoid + * an expensive search for valid estimation fixes in a huge or maybe even open-ended time range. + */ + protected NavigableSet getInternalFixesLimitedTailSet(Wind startingAt) { + final Wind endingAt = new DummyWind(startingAt.getTimePoint().plus(2*getMillisecondsOverWhichToAverageWind())); + return getInternalFixes().subSet(startingAt, /* inclusive */ true, endingAt, /* inclusive */ true); + } /** * Forwards the information about the wind fix received to the {@link #listener} which will then adjust this diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index 1256c17d96f..9bbbc6026b2 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -259,11 +259,11 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { DummyWind atTimed = new DummyWind(at); Util.Pair relativeTo = new Util.Pair(p, at); // pick one left if possible and extend to half averaging interval length - NavigableSet beforeSet = getInternalFixes().headSet(atTimed, /* inclusive */false); + NavigableSet beforeSet = getInternalFixesLimitedHeadSet(atTimed); final Iterator beforeIter = beforeSet.descendingIterator(); collectFixesInOneDirection(beforeIter, windFixesToAverage); // pick one right if possible and extend to half averaging interval length - NavigableSet afterSet = getInternalFixes().tailSet(atTimed, /* inclusive */true); + NavigableSet afterSet = getInternalFixesLimitedTailSet(atTimed); final Iterator afterIter = afterSet.iterator(); collectFixesInOneDirection(afterIter, windFixesToAverage); if (windFixesToAverage.isEmpty()) { @@ -277,6 +277,14 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } } + protected NavigableSet getInternalFixesLimitedHeadSet(Wind endingAt) { + return getInternalFixes().headSet(endingAt, /* inclusive */false); + } + + protected NavigableSet getInternalFixesLimitedTailSet(Wind startingAt) { + return getInternalFixes().tailSet(startingAt, /* inclusive */true); + } + private void collectFixesInOneDirection(final Iterator fixIter, Collection>> windFixesToAverage) { long distanceToFirst = 0; Wind nextWindFix = null; From 35eb1a8075fd078709f7390f905c90cb65b7cd93 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 16 Jun 2021 23:23:05 +0200 Subject: [PATCH 18/37] refactored BackoffTracker, using our com.sap.sse time standard types, adding Javadoc --- .../impl/ReviewedSpotsCollectionImpl.java | 3 +- .../windfinder/ReviewedSpotsCollection.java | 9 +- .../sap/sse/common/util/BackoffTracker.java | 89 +++++++++++++------ 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java index c2d2a6b8098..d8aff961b48 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java @@ -21,6 +21,7 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sse.common.Duration; import com.sap.sse.common.util.BackoffTracker; import com.sap.sse.util.HttpUrlConnectionHelper; import com.sap.sse.util.ThreadPoolUtil; @@ -51,7 +52,7 @@ public class ReviewedSpotsCollectionImpl implements ReviewedSpotsCollection { this.id = id; this.parser = new WindFinderReportParser(); this.spotsByIdCache = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().schedule(() -> { - backoffTracker = new BackoffTracker(TimeUnit.SECONDS.toMillis(5), 2); + backoffTracker = new BackoffTracker(Duration.ONE_SECOND.times(5), /* multiplier for each additional failure */ 2); final ConcurrentMap result = new ConcurrentHashMap<>(); for (final Spot spot : loadSpots()) { result.put(spot.getId(), spot); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/ReviewedSpotsCollection.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/ReviewedSpotsCollection.java index 8a822d55754..7894d337cdb 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/ReviewedSpotsCollection.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/ReviewedSpotsCollection.java @@ -7,6 +7,7 @@ import java.util.concurrent.ExecutionException; import org.json.simple.parser.ParseException; import com.sap.sse.common.WithID; +import com.sap.sse.common.util.BackoffTracker; /** * A set of {@link Spot}s that WindFinder has reviewed and selected for a dedicated request for @@ -25,9 +26,11 @@ public interface ReviewedSpotsCollection extends WithID { /** * @param cached - * if {@code true}, only those spots collections are returned that this factory has previously obtained; - * this is useful for a very fast lookup but does not guarantee that all spots collections added recently - * will be considered + * if {@code true}, only those spots collections are returned that this factory has previously obtained, + * unless all previous calls terminated abnormally with an exception in which case another attempt to + * obtain the spots will be made if not within the grace period (see also {@link BackoffTracker}); this + * is useful for a very fast lookup but does not guarantee that all spots collections added recently will + * be considered. */ Iterable getSpots(boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException; } diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/util/BackoffTracker.java b/java/com.sap.sse.common/src/com/sap/sse/common/util/BackoffTracker.java index a7e2784cdd4..06facefe72d 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/util/BackoffTracker.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/util/BackoffTracker.java @@ -1,40 +1,77 @@ package com.sap.sse.common.util; -import java.util.concurrent.TimeUnit; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +/** + * A tracker to handle continuous failures of processes. Before every process execution, call {@link #backOff()} to + * query whether there is a grace period still active since the last failure. Every failure should call + * {@link #logFailure()}, which will increase the time until {@link #backOff()} returns {@code false} again.

+ * + * The class is thread safe which is achieved by the essential methods {@link #backOff()}, {@link #logFailure()}, + * and {@link #clear()} all being {@code synchronized}. + */ public class BackoffTracker { - private Long backOffUntil; - private final int factor; - private Long currentTimeoutInMillis; - private final Long initialTimeoutInMillis; - private static final Long maxTimeout = TimeUnit.MINUTES.toMillis(5); + private TimePoint backOffUntil; + private final int backoffMultiplier; + private Duration currentTimeout; + private final Duration initialTimeout; + private final Duration maxTimeout; + + /** + * Creates a new backoff tracker, initially in the "OK" state with {@link #backOff()} returning {@code false} until + * {@link #logFailure()} is called. The maximum timeout defaults to five minutes. See also + * {@link##BackoffTracker(Duration, int, Duration)} for setting a different maximum timeout. + * + * @param initialTimeout + * the length of the first "grace period" after the first failure has been {@link #logFailure() logged} + * @param backoffMultiplier + * The factor by which the timeout will be multiplied on consecutive failures + */ + public BackoffTracker(Duration initialTimeout, int backoffMultiplier) { + this(initialTimeout, backoffMultiplier, Duration.ONE_MINUTE.times(5)); + } /** - * A tracker to handle continous failures of processes. Before every process execution call {@link #backOff()} to query whether there is a timeout. - * Every failure should call {@link #logFailure()}, which will increase the time until {@link #backOff()} returns false again. - * @param initialTimeoutInMillis - * @param backoffMultiplier: The factor by which the timeout will be multiplied on consecutive failures + * Like {@link #BackoffTracker(Duration, int)}, but allows the caller to set a specific maximum timeout. */ - public BackoffTracker(Long initialTimeoutInMillis, int backoffMultiplier) { - this.initialTimeoutInMillis = initialTimeoutInMillis; - this.factor = backoffMultiplier; + public BackoffTracker(Duration initialTimeout, int backoffMultiplier, Duration maxTimeout) { + this.initialTimeout = initialTimeout; + this.backoffMultiplier = backoffMultiplier; + this.maxTimeout = maxTimeout; } - - public void logFailure() { - if(currentTimeoutInMillis == null) { - currentTimeoutInMillis = initialTimeoutInMillis; + + /** + * Call this method after an unsuccessful attempt to invoke your service. This starts a + * grace period during which {@link #backOff()} will return {@code true}, telling you + * to not invoke the service again until {@link #backOff()} returns {@code false}. + */ + public synchronized void logFailure() { + if (currentTimeout == null) { + currentTimeout = initialTimeout; + } else { + final Duration newTimeOut = currentTimeout.times(backoffMultiplier); + currentTimeout = newTimeOut.compareTo(maxTimeout) > 0 ? maxTimeout : newTimeOut; } - final Long newTimeOut = currentTimeoutInMillis * factor; - currentTimeoutInMillis = newTimeOut >= maxTimeout ? maxTimeout : newTimeOut; - backOffUntil = System.currentTimeMillis() + currentTimeoutInMillis; + backOffUntil = TimePoint.now().plus(currentTimeout); } - - public boolean backOff() { - return backOffUntil == null ? false : backOffUntil > System.currentTimeMillis(); + + /** + * @return {@code true} if the caller should back off and currently not try to invoke the service. A {@code true} + * response indicates that a failure was {@link #logFailure() logged} previously and that a grace period + * during which the service shouldn't be invoked again since the last failure hasn't expired yet. + */ + public synchronized boolean backOff() { + return backOffUntil == null ? false : backOffUntil.after(TimePoint.now()); } - - public void clear() { - currentTimeoutInMillis = null; + + /** + * Call this method after a successful attempt of invoking your service. The grace period duration + * is reset, and {@link #backOff()} will return {@code false} from here on until {@link #logFailure()} + * is called. + */ + public synchronized void clear() { + currentTimeout = null; backOffUntil = null; } } From 16591a3d0be8066d1b44f3edb9f7f256efaec186 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jun 2021 11:31:57 +0200 Subject: [PATCH 19/37] bug5576: adjusted static wind fix for ManeuverDetectionOnKielerWoche505Race2DataTest from 240 to 245deg to let test pass again --- .../ManeuverDetectionOnKielerWoche505Race2DataTest.java | 2 +- .../maneuverdetection/impl/ManeuverDetectorImpl.java | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverDetectionOnKielerWoche505Race2DataTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverDetectionOnKielerWoche505Race2DataTest.java index 92f0632c199..9a624a53b60 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverDetectionOnKielerWoche505Race2DataTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverDetectionOnKielerWoche505Race2DataTest.java @@ -56,7 +56,7 @@ public class ManeuverDetectionOnKielerWoche505Race2DataTest extends AbstractMane new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS }); OnlineTracTracBasedTest.fixApproximateMarkPositionsForWindReadOut(getTrackedRace(), new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime())); getTrackedRace().recordWind(new WindImpl(/* position */ null, getTrackedRace().getStartOfRace(), - new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(60))), new WindSourceImpl(WindSourceType.WEB)); + new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(65))), new WindSourceImpl(WindSourceType.WEB)); logger.info("Waiting for things to settle in, such as wind updates..."); Thread.sleep(2000); logger.info("...hopefully all is settled now."); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index c5ad6c67d95..d8eb2e359ca 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -363,12 +363,12 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl { douglasPeuckerFixesGroup.get(douglasPeuckerFixesGroup.size() - 1).getTimePoint() .asMillis() + durationForDouglasPeuckerExtensionForMainCurveAnalysisInMillis), latestManeuverEnd)); - ManeuverMainCurveDetailsWithBearingSteps maneuverMainCurveDetails = computeManeuverMainCurveDetails( earliestTimePointBeforeManeuver, latestTimePointAfterManeuver, maneuverDirection); if (maneuverMainCurveDetails == null) { return null; } + // TODO wouldn't this have to be called maneuverStableCourseAndSpeedBoundaries, also the method? The variable is passed to the parameter maneuverCurveWithStableSpeedAndCourseBoundaries of the CompleteManeuverCurveImpl constructor... ManeuverCurveBoundaries maneuverUnstableCourseAndSpeedBoundaries = computeManeuverUnstableCourseAndSpeedBoundaries( maneuverMainCurveDetails, earliestManeuverStart, latestManeuverEnd); MarkPassing markPassing = getMarkPassingIfPresent(maneuverMainCurveDetails); @@ -1066,7 +1066,7 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl { ManeuverCurveBoundaryExtension stableBearingExtension = findStableBearingWithMaxAbsCourseChangeSpeed( stepsToAnalyze, false, MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS); ManeuverCurveBoundaryExtension mergedExtension = extendManeuverCurveBoundaryExtension(maneuverEnd, stableBearingExtension); - if(!isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, mergedExtension)) { + if (!isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, mergedExtension)) { maneuverEnd = mergedExtension; } return maneuverEnd != null @@ -1336,8 +1336,7 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl { highestSpeed = bestBoundariesBeforeReset.getHighestSpeed(); } if (refinedTimePointBeforeManeuver == null) { - // Should not occur, if bearingStepsToAnalyze.size() > 0 and first BearingStep.getCourseChangeInDegrees() == - // 0 + // Should not occur, if bearingStepsToAnalyze.size() > 0 and first BearingStep.getCourseChangeInDegrees() == 0 return null; } if (refinedSpeedWithBearingAfterManeuver == null) { From 20b19018a1a03e3f4537d65e35181e549734f348 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jun 2021 11:35:31 +0200 Subject: [PATCH 20/37] fixed initialization of BackoffTracker in WindFinder ReviewedSpotsCollectionImpl --- .../windfinderadapter/impl/ReviewedSpotsCollectionImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java index d8aff961b48..6504342f70b 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java @@ -51,8 +51,8 @@ public class ReviewedSpotsCollectionImpl implements ReviewedSpotsCollection { public ReviewedSpotsCollectionImpl(String id) { this.id = id; this.parser = new WindFinderReportParser(); + this.backoffTracker = new BackoffTracker(Duration.ONE_SECOND.times(5), /* multiplier for each additional failure */ 2); this.spotsByIdCache = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().schedule(() -> { - backoffTracker = new BackoffTracker(Duration.ONE_SECOND.times(5), /* multiplier for each additional failure */ 2); final ConcurrentMap result = new ConcurrentHashMap<>(); for (final Spot spot : loadSpots()) { result.put(spot.getId(), spot); From 1207ab6851b2a35140600a2d86a12a63b73d4c3c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jun 2021 11:38:05 +0200 Subject: [PATCH 21/37] bug5576: re-added timeout rule for AbstractTracTracLiveTest --- .../sap/sailing/domain/test/AbstractTracTracLiveTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java index 9cc4cb3ff9e..3f8364d6106 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java @@ -60,10 +60,10 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest { /** * Making this a method rule allows subclasses to adjust the timeout if required */ -// @Rule -// public TestRule getTimeoutRule() { -// return Timeout.millis(3 * 60 * 1000); -// } + @Rule + public TestRule getTimeoutRule() { + return Timeout.millis(3 * 60 * 1000); + } protected AbstractTracTracLiveTest() throws URISyntaxException, MalformedURLException { receivers = new HashSet(); From d0afd30de8ee683833d36c90bcfd8715d20a5e6b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jun 2021 14:55:26 +0200 Subject: [PATCH 22/37] bug5576: avoid Mockito mass-stubbing; led to OutOfMemoryErrors due to InvocationContainerImpl objects being collected --- .../WindEstimationLockingUnderLoadTest.java | 71 +++++++++++++------ .../impl/ManeuverDetectorImpl.java | 1 - 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationLockingUnderLoadTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationLockingUnderLoadTest.java index 95797d836ab..b1a8204082f 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationLockingUnderLoadTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationLockingUnderLoadTest.java @@ -1,22 +1,31 @@ package com.sap.sailing.domain.test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.logging.Logger; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.common.CompetitorRegistrationType; +import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; @@ -25,13 +34,21 @@ import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.WindImpl; import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.TrackBasedEstimationWindTrackImpl; import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; -import com.sap.sailing.domain.tracking.impl.WindTrackImpl; import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.DegreeBearingImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; @@ -59,12 +76,11 @@ public class WindEstimationLockingUnderLoadTest { private TrackBasedEstimationWindTrackImpl estimationTrack; - private WindTrackImpl measuredTrack; + private WindTrack measuredTrack; @Before public void setUp() { realWindSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, "1"); - measuredTrack = new WindTrackImpl(WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* useSpeed */ true, /* nameForReadWriteLock */ "Test wind track in "+getClass().getName()); mockedTrackedRace = mockTrackedRace(); estimationTrack = new TrackBasedEstimationWindTrackImpl(mockedTrackedRace, WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, 0.5); } @@ -77,18 +93,33 @@ public class WindEstimationLockingUnderLoadTest { } private TrackedRace mockTrackedRace() { - TrackedRace result = mock(TrackedRace.class); - RaceDefinition mockedRaceDefinition = mock(RaceDefinition.class); - when(result.getRace()).thenReturn(mockedRaceDefinition); - when(mockedRaceDefinition.getName()).thenReturn("Test Race"); - when(result.getEstimatedWindDirectionWithConfidence((TimePoint) any())).thenAnswer(new Answer>() { - public WindWithConfidence answer(InvocationOnMock invocation) { - return randomWindOrNull(); - } - }); - when(result.getOrCreateWindTrack(realWindSource)).thenReturn(measuredTrack); - when(result.getStatus()).thenReturn(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, /* loadingProgress */ 1.0)); - when(result.getMillisecondsOverWhichToAverageWind()).thenReturn(30000l); + final BoatClass boatClass = new BoatClassImpl("Some Handicap Boat Class", /* typicallyStartsUpwind */ true); + final Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, + RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass, + /* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED, /*startDate*/ null, /*endDate*/ null, /* trackedRegattaRegistry */ null, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", /* courseArea */ null, + /* controlTrackingFromStartAndFinishTimes */ false, /* autoRestartTrackingUponCompetitorSetChange */ false, + OneDesignRankingMetric::new, /* registrationLinkSecret */ UUID.randomUUID().toString()); + final TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta); + final List waypoints = Collections.emptyList(); + Course course = new CourseImpl("Test Course", waypoints); + RaceDefinition mockedRaceDefinition = new RaceDefinitionImpl("Test Race", course, boatClass, Collections.emptyMap()); + DynamicTrackedRace result = new DynamicTrackedRaceImpl(trackedRegatta, mockedRaceDefinition, + /* sidelines */ Collections.emptySet(), EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 10000, + /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 15000, + /* useInternalMarkPassingAlgorithm */ false, regatta.getRankingMetricConstructor(), /* raceLogResolver */ null, + /* trackingConnectorInfo */ null) { + private static final long serialVersionUID = 1L; + + @Override + public WindWithConfidence getEstimatedWindDirectionWithConfidence(TimePoint timePoint) { + return randomWindOrNull(); + } + }; + result.setStartTimeReceived(TimePoint.now().minus(Duration.ONE_MINUTE.times(5))); + result.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, /* loadingProgress */ 1.0)); + measuredTrack = result.getOrCreateWindTrack(realWindSource); + assertEquals(30000l, result.getMillisecondsOverWhichToAverageWind()); return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index d8eb2e359ca..032548833e5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -368,7 +368,6 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl { if (maneuverMainCurveDetails == null) { return null; } - // TODO wouldn't this have to be called maneuverStableCourseAndSpeedBoundaries, also the method? The variable is passed to the parameter maneuverCurveWithStableSpeedAndCourseBoundaries of the CompleteManeuverCurveImpl constructor... ManeuverCurveBoundaries maneuverUnstableCourseAndSpeedBoundaries = computeManeuverUnstableCourseAndSpeedBoundaries( maneuverMainCurveDetails, earliestManeuverStart, latestManeuverEnd); MarkPassing markPassing = getMarkPassingIfPresent(maneuverMainCurveDetails); From 579b351b8eeb6afa1c296e1b2ace0f0fab1484bd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jun 2021 15:17:02 +0200 Subject: [PATCH 23/37] bug5576: switched back from normal distribution for time-based confidence to hyperbolic with 5s for half confidence --- .../common/confidence/impl/PositionAndTimePointWeigher.java | 4 ++-- .../src/com/sap/sailing/domain/tracking/WindTrack.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java index ac155861d72..0b857791fc5 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/impl/PositionAndTimePointWeigher.java @@ -27,8 +27,8 @@ public class PositionAndTimePointWeigher implements Weigher { * The following is used as a "standard deviation" in a Gaussian normal distribution; in order * to still get positive confidences, a rather large duration of 30min is chosen here. */ - public static final Duration WIND_HALF_CONFIDENCE_DURATION = Duration.ONE_MINUTE.times(30); + public static final Duration WIND_HALF_CONFIDENCE_DURATION = Duration.ONE_SECOND.times(5); /** * Estimates a wind force and direction based on tracked wind data.

From 50e855351f86c872478a6d585687727fa61ba643 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 17 Jun 2021 18:15:55 +0200 Subject: [PATCH 24/37] bug5576: fixed WindTest test case after changing back how fixes are picked up --- .../com/sap/sailing/domain/test/WindTest.java | 26 ++++++++++--------- .../impl/AbstractLeaderboardWithCache.java | 4 ++- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index f6f531db760..fbff0fa0290 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -178,7 +178,9 @@ public class WindTest { /** * If the wind track has areas with no data, and wind information is requested for such an interval, * it is essential to still average over at least one fix left and one fix right, even if the - * resulting interval is longer than {@link #AVERAGING_INTERVAL_MILLIS}. + * resulting interval is longer than {@link #AVERAGING_INTERVAL_MILLIS}. Furthermore, the wind track + * is expected to pick up half the interval length worth of fixes each side starting with the first fix + * found in that direction, regardless the distance to the first fix in that direction. */ @Test public void testAveragingOfSparseWindTrack() { @@ -198,24 +200,24 @@ public class WindTest { track.add(wind5); track.add(wind6); track.add(wind7); - // expecting to pick up 0, 1000, 2000, 10000 and 30000, all with 20deg + // expecting to pick up 0, 1000, 2000, and 10000, all with 20deg PositionAssert.assertSpeedEquals(new KnotSpeedImpl(20), track.getAveragedWind(pos, new MillisecondsTimePoint(1)), 0.02); - // interval uses the two fixes to the left (0, 1000) and three to the right (2000, 10000, 30000), total length 30000 + // interval uses the two fixes to the left (0, 1000) and two to the right (2000, 10000), total length 11000 PositionAssert.assertSpeedEquals(new KnotSpeedImpl(20), track.getAveragedWind(pos, new MillisecondsTimePoint(1001)), 0.02); - // in the middle of the "hole", fetches 10000 and 30000; then adding 2000 because it's closer to 20000 than 40000; then - // adding 1000 because it's still closer to 20000 than 40000 and still within the 30s range, and the same for 0; but not - // adding 40000 then because 2000..40000=38000 > 30000; still all 20deg values + // in the middle of the "hole", fetches 10000 and 30000; then adding 2000, 1000, and 0 because it's closer than 15s to 10000; then + // adding 40000 because it's less than 15s away from 30000. The left values all are 20deg; on the right we have an average of 75deg; + // together things should end up at around 35deg (five fixes at 20deg, one fix at 130deg): final double averageFor20000 = track.getAveragedWind(pos, new MillisecondsTimePoint(20000)).getKnots(); // value is hard to predict exactly because time difference-based confidences rate fixes closer to 20000ms higher than those further away - assertEquals(20, averageFor20000, 0.02); - // right of the middle of the "hole", fetches 10000 and 30000 initially; then 40000 (distance 16000 is less than distance 22000 to 2000), - // ending up with 10000..40000=30000, so no more fix is picked up; weighted average of two 20deg fixes and one 130deg fix + assertEquals(35, averageFor20000, 2); + // right of the middle of the "hole", fetches 10000 and 30000 initially; thenthe same as for 20000, but the greater angles + // have greater weight as they are closer now to the time point (24000): final double averageFor24000 = track.getAveragedWind(pos, new MillisecondsTimePoint(24000)).getKnots(); - assertEquals(46, averageFor24000, 5); + assertEquals(37, averageFor24000, 2); assertTrue(averageFor24000 > averageFor20000); - // at least one fix will be picked up even when asking later than 30s after the last: + // at 40000 and 50000 should be picked up, resulting in an average of ~150deg final double averageFor100000 = track.getAveragedWind(pos, new MillisecondsTimePoint(100000)).getKnots(); - assertEquals(170, averageFor100000, 0.02); + assertEquals(150, averageFor100000, 2); } @Test diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java index c3dbd4c0be6..17641c4e123 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java @@ -911,7 +911,9 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock /* TODO old comment when it was still true: "because this is done only once after end of tracking" */ /* waitForLatestAnalyses (maneuver and cross track error) */ false, - legRanksCache, cache, rankingInfo); + legRanksCache, cache, + // can't re-use rankingInfo because we're now computing for a different time point: + trackedRace.getRankingMetric().getRankingInfo(end, cache)); } }); raceDetailsAtEndOfTrackingCache.put(key, raceDetails); // this way, From a61bde7a167c032bc267a9e18979082cca4230ab Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 18 Jun 2021 12:40:23 +0200 Subject: [PATCH 25/37] bug5576: consider confidence of estimated wind fixes again; lowered confidence of downwind estimation for all boat classes; fixed confidence function for number of boat classes to be less jumpy around 1..2 --- .../BearingWithConfidenceCluster.java | 2 + .../sap/sailing/domain/base/BoatClass.java | 8 +- .../domain/base/impl/BoatClassImpl.java | 24 ++---- ...timationOnKielerWoche505Race2DataTest.java | 2 +- .../TrackBasedEstimationWindTrackImpl.java | 85 +++++++++++++++++-- .../domain/tracking/impl/TrackedRaceImpl.java | 42 ++++++--- .../domain/tracking/impl/WindTrackImpl.java | 51 +++++------ 7 files changed, 146 insertions(+), 68 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/BearingWithConfidenceCluster.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/BearingWithConfidenceCluster.java index 35ed88ebdf7..b49f9c29c0e 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/BearingWithConfidenceCluster.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/confidence/BearingWithConfidenceCluster.java @@ -141,6 +141,8 @@ public class BearingWithConfidenceCluster { * by adding up the sin and cos values of the individual bearings, then computing the atan2 of the ratio. If the * combined confidence of the bearings in the cluster is 0.0, the result will contain null as * {@link BearingWithConfidence#getObject() object}. + * + * TODO bug5576 comment 40: we could analyze the cluster's variance and let greater variances reduce the confidence */ public BearingWithConfidence getAverage(RelativeTo relativeTo) { HasConfidence average = averager.getAverage(getBearings(), relativeTo); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java index 81709ca3e8c..a07c176c062 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatClass.java @@ -54,12 +54,8 @@ public interface BoatClass extends Named, IsManagedByCache>> { + protected TimePoint t; + private WindWithConfidence nextEstimatedWindWithTimeBasedConfidence; + + EstimationIterator(TimePoint t) { + this.t = t; + nextEstimatedWindWithTimeBasedConfidence = advance(); + } + + protected abstract WindWithConfidence advance(); + + @Override + public boolean hasNext() { + return nextEstimatedWindWithTimeBasedConfidence != null; + } + + protected WindWithConfidence tryToGetNext() { + return getEstimatedWindDirection(t); + } + + @Override + public WindWithConfidence> next() { + if (nextEstimatedWindWithTimeBasedConfidence == null) { + throw new NoSuchElementException(); + } + final WindWithConfidence> result = createWindWithTimeAndPositionBasedConfidence(nextEstimatedWindWithTimeBasedConfidence); + nextEstimatedWindWithTimeBasedConfidence = advance(); + return result; + } + } + /** * Limits the head set to a length of 2*{@link #getMillisecondsOverWhichToAverageWind()} to avoid * an expensive search for valid estimation fixes in a huge or maybe even open-ended time range. */ - protected NavigableSet getInternalFixesLimitedHeadSet(Wind endingAt) { - final Wind startingAt = new DummyWind(endingAt.getTimePoint().minus(2*getMillisecondsOverWhichToAverageWind())); - return getInternalFixes().subSet(startingAt, /* inclusive */ true, endingAt, /* inclusive */ false); + @Override + protected Iterator>> getInternalFixesLimitedHeadSetDescendingIterator(TimePoint endingAt) { + final TimePoint startingAt = endingAt.minus(2*getMillisecondsOverWhichToAverageWind()); + return new EstimationIterator(getInternalRawFixes().floorToResolution(endingAt)) { + @Override + protected WindWithConfidence advance() { + WindWithConfidence next; + if (t.before(startingAt)) { + next = null; + } else { + do { + next = tryToGetNext(); + t = getInternalRawFixes().lowerToResolution(t); + } while (next == null && !t.before(startingAt)); + } + return next; + } + }; } /** * Limits the tail set to a length of 2*{@link #getMillisecondsOverWhichToAverageWind()} to avoid * an expensive search for valid estimation fixes in a huge or maybe even open-ended time range. */ - protected NavigableSet getInternalFixesLimitedTailSet(Wind startingAt) { - final Wind endingAt = new DummyWind(startingAt.getTimePoint().plus(2*getMillisecondsOverWhichToAverageWind())); - return getInternalFixes().subSet(startingAt, /* inclusive */ true, endingAt, /* inclusive */ true); + @Override + protected Iterator>> getInternalFixesLimitedTailSetIterator(TimePoint startingAt) { + final TimePoint endingAt = startingAt.plus(2*getMillisecondsOverWhichToAverageWind()); + return new EstimationIterator(getInternalRawFixes().ceilingToResolution(startingAt)) { + @Override + protected WindWithConfidence advance() { + WindWithConfidence next; + if (t.after(endingAt)) { + next = null; + } else { + do { + next = tryToGetNext(); + t = getInternalRawFixes().higherToResolution(t); + } while (next == null && !t.after(endingAt)); + } + return next; + } + }; + } + + protected WindWithConfidenceImpl> createWindWithTimeAndPositionBasedConfidence( + final WindWithConfidence estimatedWindWithTimeBasedConfidence) { + return new WindWithConfidenceImpl>( + estimatedWindWithTimeBasedConfidence.getObject(), estimatedWindWithTimeBasedConfidence.getConfidence(), + new Pair<>(estimatedWindWithTimeBasedConfidence.getObject().getPosition(), estimatedWindWithTimeBasedConfidence.getObject().getTimePoint()), + isUseSpeed()); } /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 4f2d3b2f110..d53d66d6268 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -2416,31 +2416,50 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl return estimatedWindWithConfidence == null ? null : estimatedWindWithConfidence.getObject(); } + /** + * A function that starts with 0.1 as confidence if the number of boats in the smallest cluster is 1; growing + * steadily and converging towards 1.0 as the number grows towards positive infinity (or {@link Integer#MAX_VALUE} + * to be more precise). The derivative at {@code numberOfBoatsInSmallestCluster==1} is 0.1 (so as if it was doubling + * on its way from one to two boats). Modeling this as the function {@code f(n) := 1 + b/(n+c)} we get a solution + * for b and c such that {@code c=8} and {@code b = -0.9-0.9*c = -0.9-7.2 = -8.1} and hence + * + *

+     *     f(n) := 1 - 8.1/(n+8)
+     * 
+ * + * @param numberOfBoatsInSmallestCluster + * must not be less than 1 + */ + private double getConfidenceMultiplierForClusterSize(int numberOfBoatsInSmallestCluster) { + return 1.0 - 8.1/(8.0 + numberOfBoatsInSmallestCluster); + } + @Override public WindWithConfidence getEstimatedWindDirectionWithConfidence(TimePoint timePoint) { - DummyMarkPassingWithTimePointOnly dummyMarkPassingForNow = new DummyMarkPassingWithTimePointOnly(timePoint); - Weigher weigher = ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( + final DummyMarkPassingWithTimePointOnly dummyMarkPassingForNow = new DummyMarkPassingWithTimePointOnly(timePoint); + final Weigher weigher = ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero getMillisecondsOverWhichToAverageSpeed(), /* minimum confidence */0.0000000001); - Map, ScalablePosition>> bearings = clusterBearingsByLegType( + final Map, ScalablePosition>> bearings = clusterBearingsByLegType( timePoint, dummyMarkPassingForNow, weigher); // use the minimum confidence of the four "quadrants" as the result's confidence BearingWithConfidenceImpl reversedUpwindAverage = null; - int upwindNumberOfRelevantBoats = 0; double confidence = 0; BearingWithConfidence resultBearing = null; ScalablePosition scaledPosition = null; int numberOfFixesConsideredForScaledPosition = 0; - Set estimationExcluded = new HashSet<>(); + final Set estimationExcluded = new HashSet<>(); estimationExcluded.addAll(getWindSources(WindSourceType.TRACK_BASED_ESTIMATION)); estimationExcluded.addAll(getWindSources(WindSourceType.COURSE_BASED)); if (bearings != null) { + // TODO factor out the commonalities between UPWIND and DOWNWIND to reduce code duplication + int upwindNumberOfRelevantBoats = 0; int numberOfFixesUpwind = bearings.get(LegType.UPWIND).getA().size(); if (numberOfFixesUpwind > 0) { - ScalablePosition upwindPosition = bearings.get(LegType.UPWIND).getB(); - Pair minimumAngleBetweenDifferentTacksUpwindWithConfidence = getMinimumAngleBetweenDifferentTacksUpwind(getWind( + final ScalablePosition upwindPosition = bearings.get(LegType.UPWIND).getB(); + final Pair minimumAngleBetweenDifferentTacksUpwindWithConfidence = getMinimumAngleBetweenDifferentTacksUpwind(getWind( upwindPosition.divide(numberOfFixesUpwind), timePoint, estimationExcluded)); - BearingWithConfidenceCluster[] bearingClustersUpwind = bearings + final BearingWithConfidenceCluster[] bearingClustersUpwind = bearings .get(LegType.UPWIND) .getA() .splitInTwo( @@ -2452,7 +2471,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl upwindNumberOfRelevantBoats = Math.min(bearingClustersUpwind[0].size(), bearingClustersUpwind[1].size()); confidence = Math.min(average0.getConfidence(), average1.getConfidence()) - * getRace().getBoatClass().getUpwindWindEstimationConfidence(upwindNumberOfRelevantBoats) + * getRace().getBoatClass().getUpwindWindEstimationConfidence() + * getConfidenceMultiplierForClusterSize(upwindNumberOfRelevantBoats) * minimumAngleBetweenDifferentTacksUpwindWithConfidence.getB(); reversedUpwindAverage = new BearingWithConfidenceImpl(average0.getObject() .middle(average1.getObject()).reverse(), confidence, timePoint); @@ -2479,8 +2499,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl downwindNumberOfRelevantBoats = Math.min(bearingClustersDownwind[0].size(), bearingClustersDownwind[1].size()); confidence = Math.min(average0.getConfidence(), average1.getConfidence()) - * getRace().getBoatClass().getDownwindWindEstimationConfidence( - downwindNumberOfRelevantBoats) + * getRace().getBoatClass().getDownwindWindEstimationConfidence() + * getConfidenceMultiplierForClusterSize(downwindNumberOfRelevantBoats) * minimumAngleBetweenDifferentTacksDownwindWithConfidence.getB(); downwindAverage = new BearingWithConfidenceImpl(average0.getObject().middle( average1.getObject()), confidence, timePoint); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index 9bbbc6026b2..9f946282f82 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.NavigableSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,6 +34,7 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Timed; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.util.MappingIterator; import com.sap.sse.shared.util.impl.ArrayListNavigableSet; /** @@ -250,21 +250,18 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { * if null, the averaged position of the original wind fixes is returned; otherwise, * p is used as the result's position and may be used for confidence determination. */ - protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { + protected WindWithConfidence> getAveragedWindUnsynchronized(Position p, TimePoint at) { lockForRead(); try { - List>> windFixesToAverage = new ArrayList>>(); + List>> windFixesToAverage = new ArrayList>>(); // don't measure speed with separate confidence; return confidence obtained from averaging bearings - ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE.createWindAverager(weigher); - DummyWind atTimed = new DummyWind(at); + ConfidenceBasedWindAverager> windAverager = ConfidenceFactory.INSTANCE.createWindAverager(weigher); Util.Pair relativeTo = new Util.Pair(p, at); // pick one left if possible and extend to half averaging interval length - NavigableSet beforeSet = getInternalFixesLimitedHeadSet(atTimed); - final Iterator beforeIter = beforeSet.descendingIterator(); + final Iterator>> beforeIter = getInternalFixesLimitedHeadSetDescendingIterator(at); collectFixesInOneDirection(beforeIter, windFixesToAverage); // pick one right if possible and extend to half averaging interval length - NavigableSet afterSet = getInternalFixesLimitedTailSet(atTimed); - final Iterator afterIter = afterSet.iterator(); + final Iterator>> afterIter = getInternalFixesLimitedTailSetIterator(at); collectFixesInOneDirection(afterIter, windFixesToAverage); if (windFixesToAverage.isEmpty()) { return null; @@ -277,23 +274,24 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } } - protected NavigableSet getInternalFixesLimitedHeadSet(Wind endingAt) { - return getInternalFixes().headSet(endingAt, /* inclusive */false); + protected Iterator>> getInternalFixesLimitedHeadSetDescendingIterator(TimePoint endingAt) { + return new MappingIterator<>(getInternalFixes().headSet(new DummyWind(endingAt), /* inclusive */false).descendingIterator(), wind->createWindWithConfidence(wind)); } - protected NavigableSet getInternalFixesLimitedTailSet(Wind startingAt) { - return getInternalFixes().tailSet(startingAt, /* inclusive */true); + protected Iterator>> getInternalFixesLimitedTailSetIterator(TimePoint startingAt) { + return new MappingIterator<>(getInternalFixes().tailSet(new DummyWind(startingAt), /* inclusive */true).iterator(), wind->createWindWithConfidence(wind)); } - private void collectFixesInOneDirection(final Iterator fixIter, Collection>> windFixesToAverage) { + private void collectFixesInOneDirection(final Iterator>> fixIter, + Collection>> windFixesToAverage) { long distanceToFirst = 0; - Wind nextWindFix = null; + WindWithConfidence> nextWindFix = null; final TimePoint firstTimePointInDirection; if (fixIter.hasNext()) { nextWindFix = fixIter.next(); - firstTimePointInDirection = nextWindFix.getTimePoint(); - distanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getTimePoint().asMillis()); - windFixesToAverage.add(createWindWithConfidence(nextWindFix)); + firstTimePointInDirection = nextWindFix.getObject().getTimePoint(); + distanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getObject().getTimePoint().asMillis()); + windFixesToAverage.add(nextWindFix); } else { firstTimePointInDirection = null; } @@ -301,18 +299,18 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { long newDistanceToFirst; if (fixIter.hasNext()) { nextWindFix = fixIter.next(); - newDistanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getTimePoint().asMillis()); + newDistanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getObject().getTimePoint().asMillis()); } else { nextWindFix = null; newDistanceToFirst = distanceToFirst; } // go up to half the interval: while (nextWindFix != null && newDistanceToFirst <= getMillisecondsOverWhichToAverageWind()/2) { - windFixesToAverage.add(createWindWithConfidence(nextWindFix)); + windFixesToAverage.add(nextWindFix); distanceToFirst = newDistanceToFirst; if (fixIter.hasNext()) { nextWindFix = fixIter.next(); - newDistanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getTimePoint().asMillis()); + newDistanceToFirst = Math.abs(firstTimePointInDirection.asMillis() - nextWindFix.getObject().getTimePoint().asMillis()); } else { nextWindFix = null; newDistanceToFirst = distanceToFirst; @@ -325,9 +323,14 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } private WindWithConfidenceImpl> createWindWithConfidence(Wind wind) { - return new WindWithConfidenceImpl>(wind, - getConfidenceOfInternalWindFixUnsynchronized(wind), new Util.Pair(wind.getPosition(), wind - .getTimePoint()), useSpeed); + return new WindWithConfidenceImpl>(wind, + getConfidenceOfInternalWindFixUnsynchronized(wind), + /* relativeTo */ new Pair(wind.getPosition(), wind.getTimePoint()), + useSpeed); + } + + protected boolean isUseSpeed() { + return useSpeed; } /** From d376a7556a1522637db00d98d1e682339c737094 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 18 Jun 2021 15:53:14 +0200 Subject: [PATCH 26/37] bug5576: added a short wait / Thread.sleep in the testDynamicRaceDeletion test which has failed often now --- .../test/leaderboard/TestLeaderboardConfiguration.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java index 33a60a5b280..c268daac7c4 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java @@ -6,6 +6,8 @@ import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import org.junit.Before; import org.junit.Ignore; @@ -31,6 +33,7 @@ import com.sap.sailing.selenium.pages.leaderboard.LeaderboardTablePO.Leaderboard import com.sap.sailing.selenium.test.AbstractSeleniumTest; public class TestLeaderboardConfiguration extends AbstractSeleniumTest { + private static final Logger logger = Logger.getLogger(TestLeaderboardConfiguration.class.getName()); private static final String IDM_5O5_2013_JSON_URL = "http://traclive.dk/events/event_20130917_IDMO/jsonservice.php"; //$NON-NLS-1$ @@ -135,6 +138,11 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest { SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); seriesDialog.deleteRace("D3"); seriesDialog.pressOk(true, false); + try { + Thread.sleep(2); // wait for the regatta details to have been updated with the new series information + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Interrupted!", e); + } final List expectedRaces = Arrays.asList("D1", "D2", "D4", "D5"); regattaDetails.waitForRacesOfSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME, expectedRaces); // Now we can check the result with our expectation From 57a943ab20c04316cea9e7cda2b93c256380e0a8 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 18 Jun 2021 18:37:48 +0200 Subject: [PATCH 27/37] bug5452: refactored wind importing micro-framework and provide position as wind source ID for GRIB import; Performance upon trying to display this on a map still seems extremely "sluggish". Probably we need to check how this data unfolds in memory and how it is being used to, e.g., create the animated streamlets. --- .../windimport/AbstractWindImporter.java | 45 ++++++++++++------- .../windimport/bravo/BravoWindImporter.java | 12 ++--- .../windimport/expedition/WindImporter.java | 11 +++-- .../windimport/grib/GribWindImporter.java | 24 ++++++++-- .../windimport/nmea/NmeaWindImporter.java | 13 +++--- 5 files changed, 73 insertions(+), 32 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/AbstractWindImporter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/AbstractWindImporter.java index 5e4ec432352..d00c75b5be8 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/AbstractWindImporter.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/AbstractWindImporter.java @@ -7,6 +7,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.apache.commons.fileupload.FileItem; import org.apache.shiro.SecurityUtils; @@ -135,7 +136,7 @@ public abstract class AbstractWindImporter { public void importWindForUploadRequest(RacingEventService service, WindImportResult windImportResult, UploadRequest uploadRequest) throws IOException, InterruptedException, FormatNotSupportedException { - WindSource windSource = getWindSource(uploadRequest); + WindSource windSource = getDefaultWindSource(uploadRequest); List trackedRaces = new ArrayList(); if (uploadRequest.races.size() > 0) { for (RegattaAndRaceIdentifier raceEntry : uploadRequest.races) { @@ -164,28 +165,42 @@ public abstract class AbstractWindImporter { importWindToWindSourceAndTrackedRaces(service, windImportResult, windSource, trackedRaces, streamsWithFilenames); } - public void importWindToWindSourceAndTrackedRaces(RacingEventService service, WindImportResult windImportResult, WindSource windSource, + /** + * @param defaultWindSource + * the wind source to use for the wind fixes read using the {@link #importWind(WindSource, Map)} method for those + * fixes for which no explicit wind source has been returned by the {@link #importWind(WindSource, Map)} method. + */ + public void importWindToWindSourceAndTrackedRaces(RacingEventService service, WindImportResult windImportResult, WindSource defaultWindSource, List trackedRaces, final Map streamsWithFilenames) throws IOException, InterruptedException, FormatNotSupportedException { - Iterable windFixes = importWind(streamsWithFilenames); - if (!Util.isEmpty(windFixes)) { - for (DynamicTrackedRace trackedRace : trackedRaces) { - RegattaAndRaceIdentifier raceIdentifier = trackedRace.getRaceIdentifier(); - RaceEntry raceEntry = windImportResult.addRaceEntry(raceIdentifier.getRegattaName(), - raceIdentifier.getRaceName()); - for (Wind wind : windFixes) { - windImportResult.update(wind); - if (trackedRace.recordWind(wind, windSource)) { - raceEntry.update(wind); + Map> windFixes = importWind(defaultWindSource, streamsWithFilenames); + for (final Entry> windForSource : windFixes.entrySet()) { + if (!Util.isEmpty(windForSource.getValue())) { + for (DynamicTrackedRace trackedRace : trackedRaces) { + RegattaAndRaceIdentifier raceIdentifier = trackedRace.getRaceIdentifier(); + RaceEntry raceEntry = windImportResult.addRaceEntry(raceIdentifier.getRegattaName(), + raceIdentifier.getRaceName()); + for (Wind wind : windForSource.getValue()) { + windImportResult.update(wind); + if (trackedRace.recordWind(wind, windForSource.getKey() == null ? defaultWindSource : windForSource.getKey())) { + raceEntry.update(wind); + } } + service.getPolarDataService().insertExistingFixes(trackedRace); } - service.getPolarDataService().insertExistingFixes(trackedRace); } } } - protected abstract Iterable importWind(Map streamsWithFilenames) + /** + * @param defaultWindSource + * the default wind source to use as the key of the map returned; implementations may, however, use this + * default wind source only as a copy template to produce finer-grained wind sources based on what the + * import stream contains + * @return a map whose values are the wind fixes imported, keyed by the {@link WindSource} to which to add them. + */ + protected abstract Map> importWind(WindSource defaultWindSource, Map streamsWithFilenames) throws IOException, InterruptedException, FormatNotSupportedException; - protected abstract WindSource getWindSource(UploadRequest uploadRequest); + protected abstract WindSource getDefaultWindSource(UploadRequest uploadRequest); } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/bravo/BravoWindImporter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/bravo/BravoWindImporter.java index b0b4b15b5c7..ca807ed79ea 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/bravo/BravoWindImporter.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/bravo/BravoWindImporter.java @@ -36,7 +36,7 @@ public class BravoWindImporter extends AbstractWindImporter { private static final Logger logger = Logger.getLogger(BravoWindImporter.class.getName()); @Override - protected WindSource getWindSource(UploadRequest uploadRequest) { + protected WindSource getDefaultWindSource(UploadRequest uploadRequest) { final WindSource windSource; final String sourceName; logger.info("Importing Bravo wind data from "+uploadRequest.files); @@ -50,19 +50,21 @@ public class BravoWindImporter extends AbstractWindImporter { } @Override - protected Iterable importWind(Map inputStreamsAndFilenames) throws IOException, InterruptedException, FormatNotSupportedException { - final Iterable result; + protected Map> importWind(WindSource defaultWindSource, Map inputStreamsAndFilenames) throws IOException, InterruptedException, FormatNotSupportedException { + final Iterable windFixes; if (inputStreamsAndFilenames != null && inputStreamsAndFilenames.size() == 1) { logger.info("Reading Bravo wind data from "+inputStreamsAndFilenames.values().iterator().next()); - result = readWind(inputStreamsAndFilenames.values().iterator().next(), inputStreamsAndFilenames.keySet().iterator().next()); + windFixes = readWind(inputStreamsAndFilenames.values().iterator().next(), inputStreamsAndFilenames.keySet().iterator().next()); } else { final List windList = new LinkedList<>(); for (final Entry inputStreamAndFileName : inputStreamsAndFilenames.entrySet()) { logger.info("Reading Bravo wind data from "+inputStreamAndFileName.getValue()); Util.addAll(readWind(inputStreamAndFileName.getValue(), inputStreamAndFileName.getKey()), windList); } - result = windList; + windFixes = windList; } + final Map> result = new HashMap<>(); + result.put(defaultWindSource, windFixes); return result; } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/expedition/WindImporter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/expedition/WindImporter.java index 47b6ce58ed0..2871e224598 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/expedition/WindImporter.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/expedition/WindImporter.java @@ -3,6 +3,7 @@ package com.sap.sailing.server.gateway.windimport.expedition; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -20,7 +21,7 @@ import com.sap.sse.common.Util; public class WindImporter extends AbstractWindImporter { @Override - protected WindSource getWindSource(UploadRequest uploadRequest) { + protected WindSource getDefaultWindSource(UploadRequest uploadRequest) { WindSource windSource; if (uploadRequest.boatId == null) { windSource = new WindSourceImpl(WindSourceType.EXPEDITION); @@ -31,16 +32,18 @@ public class WindImporter extends AbstractWindImporter { } @Override - protected Iterable importWind(Map streamsWithFilenames) throws IOException, FormatNotSupportedException { - final List result = new ArrayList<>(); + protected Map> importWind(WindSource defaultWindSource, Map streamsWithFilenames) throws IOException, FormatNotSupportedException { + final List windFixes = new ArrayList<>(); for (final Map.Entry entry : streamsWithFilenames.entrySet()) { CompressedStreamsUtil.handlePotentiallyCompressedFiles(entry.getValue(), entry.getKey(), new ExpeditionImportFileHandler() { @Override protected void handleExpeditionFile(String fileName, InputStream inputStream) throws IOException { - Util.addAll(WindLogParser.importWind(inputStream), result); + Util.addAll(WindLogParser.importWind(inputStream), windFixes); } }); } + final Map> result = new HashMap<>(); + result.put(defaultWindSource, windFixes); return result; } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/grib/GribWindImporter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/grib/GribWindImporter.java index 7fac1ab0d18..7d4d4857480 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/grib/GribWindImporter.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/grib/GribWindImporter.java @@ -2,10 +2,13 @@ package com.sap.sailing.server.gateway.windimport.grib; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; @@ -14,12 +17,13 @@ import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.grib.GribWindField; import com.sap.sailing.grib.GribWindFieldFactory; import com.sap.sailing.server.gateway.windimport.AbstractWindImporter; +import com.sap.sse.common.Util; public class GribWindImporter extends AbstractWindImporter { private static final Logger logger = Logger.getLogger(GribWindImporter.class.getName()); @Override - protected WindSource getWindSource(UploadRequest uploadRequest) { + protected WindSource getDefaultWindSource(UploadRequest uploadRequest) { WindSource windSource; if (uploadRequest.boatId == null) { windSource = new WindSourceImpl(WindSourceType.WEB); @@ -30,8 +34,22 @@ public class GribWindImporter extends AbstractWindImporter { } @Override - protected Iterable importWind(Map inputStreamsAndFilenames) throws IOException { + protected Map> importWind(WindSource defaultWindSource, Map inputStreamsAndFilenames) throws IOException { final GribWindField windField = GribWindFieldFactory.INSTANCE.createGribWindFieldFromStreams(logger, Level.INFO, inputStreamsAndFilenames); - return windField.getAllWindFixes(); + final Map> result = new HashMap<>(); + final Map windSourcesByPosition = new HashMap<>(); + final Map> writableResult = new HashMap<>(); + for (final Wind windFix : windField.getAllWindFixes()) { + final WindSource windSourceForFix = getOrCreateWindSourceForPosition(windSourcesByPosition, windFix.getPosition(), defaultWindSource); + Util.addToValueSet(writableResult, windSourceForFix, windFix); + } + result.putAll(writableResult); + return result; + } + + private WindSource getOrCreateWindSourceForPosition(Map windSourcesByPosition, Position position, WindSource defaultWindSource) { + return windSourcesByPosition.computeIfAbsent(position, p-> + new WindSourceWithAdditionalID(defaultWindSource.getType(), + defaultWindSource.getId()==null ? p.toString() : defaultWindSource.getId()+"@"+p.toString())); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/nmea/NmeaWindImporter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/nmea/NmeaWindImporter.java index 5f32a2300a3..11344bb7a47 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/nmea/NmeaWindImporter.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/windimport/nmea/NmeaWindImporter.java @@ -2,6 +2,7 @@ package com.sap.sailing.server.gateway.windimport.nmea; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -23,7 +24,7 @@ public class NmeaWindImporter extends AbstractWindImporter { private static final Logger logger = Logger.getLogger(NmeaWindImporter.class.getName()); @Override - protected WindSource getWindSource(UploadRequest uploadRequest) { + protected WindSource getDefaultWindSource(UploadRequest uploadRequest) { final WindSource windSource; final String sourceName; logger.info("Importing NMEA wind data from "+uploadRequest.files); @@ -37,19 +38,21 @@ public class NmeaWindImporter extends AbstractWindImporter { } @Override - protected Iterable importWind(Map inputStreamsAndFilenames) throws IOException, InterruptedException { - final Iterable result; + protected Map> importWind(WindSource defaultWindSource, Map inputStreamsAndFilenames) throws IOException, InterruptedException { + final Iterable windFixes; if (inputStreamsAndFilenames != null && inputStreamsAndFilenames.size() == 1) { logger.info("Reading NMEA wind data from "+inputStreamsAndFilenames.values().iterator().next()); - result = readWind(inputStreamsAndFilenames.values().iterator().next(), inputStreamsAndFilenames.keySet().iterator().next()); + windFixes = readWind(inputStreamsAndFilenames.values().iterator().next(), inputStreamsAndFilenames.keySet().iterator().next()); } else { final List windList = new LinkedList<>(); for (final Entry inputStreamAndFileName : inputStreamsAndFilenames.entrySet()) { logger.info("Reading NMEA wind data from "+inputStreamAndFileName.getValue()); Util.addAll(readWind(inputStreamAndFileName.getValue(), inputStreamAndFileName.getKey()), windList); } - result = windList; + windFixes = windList; } + final Map> result = new HashMap<>(); + result.put(defaultWindSource, windFixes); return result; } From 2dab20d43b882392e34a1665d05e6b5b9cd12002 Mon Sep 17 00:00:00 2001 From: Udo Wessels Date: Fri, 18 Jun 2021 19:50:15 +0200 Subject: [PATCH 28/37] bug5576: Quickfix for Selenium test failure on Hudson build. - reverted Sleep --- .../regatta/RegattaDetailsCompositePO.java | 17 ++++++++++++----- .../TestLeaderboardConfiguration.java | 5 ----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java index acfa69abe74..c732809c123 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/regatta/RegattaDetailsCompositePO.java @@ -4,9 +4,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import com.google.common.base.Objects; import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.PageArea; @@ -88,15 +90,20 @@ public class RegattaDetailsCompositePO extends PageArea { } public List getRaceNames(String seriesName) { - final DataEntryPO seriesEntry = findSeries(seriesName); - final String racesColumnContent = seriesEntry.getColumnContent("Races"); - if (racesColumnContent != null && ! racesColumnContent.isEmpty()) { - return Arrays.asList(racesColumnContent.split(", ")); + try { + final DataEntryPO seriesEntry = findSeries(seriesName); + final String racesColumnContent = seriesEntry.getColumnContent("Races"); + if (racesColumnContent != null && ! racesColumnContent.isEmpty()) { + return Arrays.asList(racesColumnContent.split(", ")); + } + } catch(StaleElementReferenceException e) { + // DOM is currently changing and therefore elements are no longer attached or document was refreshed. + return null; } return Collections.emptyList(); } public void waitForRacesOfSeries(final String series, final List races) { - waitUntil(() -> getRaceNames(series).equals(races)); + waitUntil(() -> Objects.equal(getRaceNames(series), races)); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java index c268daac7c4..785c3bad0f1 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java @@ -138,11 +138,6 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest { SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); seriesDialog.deleteRace("D3"); seriesDialog.pressOk(true, false); - try { - Thread.sleep(2); // wait for the regatta details to have been updated with the new series information - } catch (InterruptedException e) { - logger.log(Level.SEVERE, "Interrupted!", e); - } final List expectedRaces = Arrays.asList("D1", "D2", "D4", "D5"); regattaDetails.waitForRacesOfSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME, expectedRaces); // Now we can check the result with our expectation From f7d1e33b5fae973105da62bb4987b7a4b0479687 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 18 Jun 2021 23:47:55 +0200 Subject: [PATCH 29/37] release notes for bug5576 (wind dampening) --- .../places/whatsnew/resources/RaceCommitteeAppNotes.html | 4 ++++ .../places/whatsnew/resources/SailingAnalyticsNotes.html | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html index 4f8c688757c..5748a70f3e0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html @@ -4,6 +4,10 @@

What's New - SAP Sailing Race Manager

+
June 2021
+
    +
  • Events without proper start or end dates no longer cause problems.
  • +
April 2021
  • Fixed a problem with the date picker for blue flag events; previously, changing the date diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html index 9e711cd95a1..a726e83cd13 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html @@ -7,6 +7,12 @@
    June 2021
    • The advantage line now also (again) shows after the first competitor has crossed the finish line.
    • +
    • Dampening of wind, in particular the estimation from courses over ground (COG), + has been improved. The confidences inferred from cluster sizes sailing on separate tacks + are now growing more smoothly for small fleet sizes and hence cause less unexpected + "jumps" in estimated wind directions. Base confidence of wind estimations, compared + to sensor-based measurements, has been slightly reduced, as has the base confidence for + estimations from downwind legs.
    May 2021
      From ba9535e72fda74a4b20dc10b49f38188654d57c2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 19 Jun 2021 01:37:05 +0200 Subject: [PATCH 30/37] bug5576: remove logger stuff, avoid "unused" warnings --- .../GWT Dashboards DevMode.launch | 94 +++---- .../GWT Dashboards SDM.launch | 104 ++++---- .../GWT Sailing DevMode.launch | 236 ++++++++--------- ... Sailing SDM (Home+Admin+Raceboard).launch | 222 ++++++++-------- .../GWT Sailing SDM ManagementConsole.launch | 216 +++++++-------- .../GWT Sailing SDM.launch | 248 +++++++++--------- .../TestLeaderboardConfiguration.java | 3 - .../GWT xdStorage Sample SDM.launch | 110 ++++---- .../GWT Security DevMode.launch | 214 +++++++-------- .../GWT Security SDM.launch | 218 +++++++-------- 10 files changed, 831 insertions(+), 834 deletions(-) diff --git a/java/com.sap.sailing.dashboards.gwt/GWT Dashboards DevMode.launch b/java/com.sap.sailing.dashboards.gwt/GWT Dashboards DevMode.launch index 82541c2b777..f930818bbb8 100755 --- a/java/com.sap.sailing.dashboards.gwt/GWT Dashboards DevMode.launch +++ b/java/com.sap.sailing.dashboards.gwt/GWT Dashboards DevMode.launch @@ -1,50 +1,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.dashboards.gwt/GWT Dashboards SDM.launch b/java/com.sap.sailing.dashboards.gwt/GWT Dashboards SDM.launch index 0e40747c87a..e33710bdb91 100755 --- a/java/com.sap.sailing.dashboards.gwt/GWT Dashboards SDM.launch +++ b/java/com.sap.sailing.dashboards.gwt/GWT Dashboards SDM.launch @@ -1,55 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/GWT Sailing DevMode.launch b/java/com.sap.sailing.gwt.ui/GWT Sailing DevMode.launch index 574d3bbb77c..964428b8c6e 100755 --- a/java/com.sap.sailing.gwt.ui/GWT Sailing DevMode.launch +++ b/java/com.sap.sailing.gwt.ui/GWT Sailing DevMode.launch @@ -1,121 +1,121 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/GWT Sailing SDM (Home+Admin+Raceboard).launch b/java/com.sap.sailing.gwt.ui/GWT Sailing SDM (Home+Admin+Raceboard).launch index 40cc8026f00..451b0abcafd 100644 --- a/java/com.sap.sailing.gwt.ui/GWT Sailing SDM (Home+Admin+Raceboard).launch +++ b/java/com.sap.sailing.gwt.ui/GWT Sailing SDM (Home+Admin+Raceboard).launch @@ -1,114 +1,114 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/GWT Sailing SDM ManagementConsole.launch b/java/com.sap.sailing.gwt.ui/GWT Sailing SDM ManagementConsole.launch index 9b555890146..3813def89a1 100644 --- a/java/com.sap.sailing.gwt.ui/GWT Sailing SDM ManagementConsole.launch +++ b/java/com.sap.sailing.gwt.ui/GWT Sailing SDM ManagementConsole.launch @@ -1,111 +1,111 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/GWT Sailing SDM.launch b/java/com.sap.sailing.gwt.ui/GWT Sailing SDM.launch index a7f04ea757d..95de76f9203 100755 --- a/java/com.sap.sailing.gwt.ui/GWT Sailing SDM.launch +++ b/java/com.sap.sailing.gwt.ui/GWT Sailing SDM.launch @@ -1,127 +1,127 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java index 785c3bad0f1..33a60a5b280 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java @@ -6,8 +6,6 @@ import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; import org.junit.Before; import org.junit.Ignore; @@ -33,7 +31,6 @@ import com.sap.sailing.selenium.pages.leaderboard.LeaderboardTablePO.Leaderboard import com.sap.sailing.selenium.test.AbstractSeleniumTest; public class TestLeaderboardConfiguration extends AbstractSeleniumTest { - private static final Logger logger = Logger.getLogger(TestLeaderboardConfiguration.class.getName()); private static final String IDM_5O5_2013_JSON_URL = "http://traclive.dk/events/event_20130917_IDMO/jsonservice.php"; //$NON-NLS-1$ diff --git a/java/com.sap.sse.gwt/GWT xdStorage Sample SDM.launch b/java/com.sap.sse.gwt/GWT xdStorage Sample SDM.launch index bac183e2b41..7575c54ca54 100755 --- a/java/com.sap.sse.gwt/GWT xdStorage Sample SDM.launch +++ b/java/com.sap.sse.gwt/GWT xdStorage Sample SDM.launch @@ -1,58 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.security.ui/GWT Security DevMode.launch b/java/com.sap.sse.security.ui/GWT Security DevMode.launch index 49029705c6b..26167ca2c75 100755 --- a/java/com.sap.sse.security.ui/GWT Security DevMode.launch +++ b/java/com.sap.sse.security.ui/GWT Security DevMode.launch @@ -1,110 +1,110 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.security.ui/GWT Security SDM.launch b/java/com.sap.sse.security.ui/GWT Security SDM.launch index f57e4fce9fc..f00bddacdfd 100755 --- a/java/com.sap.sse.security.ui/GWT Security SDM.launch +++ b/java/com.sap.sse.security.ui/GWT Security SDM.launch @@ -1,112 +1,112 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 2c622e5b459c9c0bba577ce7d92cb9ebce55d2ff Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 19 Jun 2021 12:45:03 +0200 Subject: [PATCH 31/37] patched into all project files to avoid info/warning in problems view about no grammar constraint --- .project | 1 + ios/.project | 1 + java/.project | 1 + java/com.amazon.aws.aws-java-api.updatesite/.project | 1 + java/com.github.branflake2267.gwt-maps-api/.project | 1 + java/com.google.gwt.ajaxloader/.project | 1 + java/com.google.gwt.dev/.project | 1 + java/com.google.gwt.servlet/.project | 1 + java/com.google.gwt.user/.project | 1 + java/com.googlecode.java-diff-utils.test/.project | 1 + java/com.googlecode.java-diff-utils/.project | 1 + java/com.googlecode.mgwt/.project | 1 + java/com.sap.sailing.barbados.resultimport.test/.project | 1 + java/com.sap.sailing.barbados.resultimport/.project | 1 + java/com.sap.sailing.competitorimport/.project | 1 + java/com.sap.sailing.dashboards.gwt/.project | 1 + java/com.sap.sailing.datamining.provider/.project | 1 + java/com.sap.sailing.datamining.shared/.project | 1 + java/com.sap.sailing.datamining.test/.project | 1 + java/com.sap.sailing.datamining/.project | 1 + java/com.sap.sailing.declination.test/.project | 1 + java/com.sap.sailing.declination/.project | 1 + java/com.sap.sailing.domain.bravoadapter/.project | 1 + java/com.sap.sailing.domain.common/.project | 1 + java/com.sap.sailing.domain.deckmanadapter.test/.project | 1 + java/com.sap.sailing.domain.deckmanadapter/.project | 1 + java/com.sap.sailing.domain.expeditionadapter/.project | 1 + java/com.sap.sailing.domain.igtimiadapter.gateway/.project | 1 + java/com.sap.sailing.domain.igtimiadapter.persistence/.project | 1 + java/com.sap.sailing.domain.igtimiadapter.test/.project | 1 + java/com.sap.sailing.domain.igtimiadapter/.project | 1 + java/com.sap.sailing.domain.oceanraceadapter/.project | 1 + java/com.sap.sailing.domain.persistence/.project | 1 + java/com.sap.sailing.domain.racelogtrackingadapter.test/.project | 1 + .../.project | 1 + java/com.sap.sailing.domain.racelogtrackingadapter/.project | 1 + java/com.sap.sailing.domain.shared.android.test/.project | 1 + java/com.sap.sailing.domain.shared.android/.project | 1 + .../.project | 1 + java/com.sap.sailing.domain.swisstimingadapter.test/.project | 1 + java/com.sap.sailing.domain.swisstimingadapter/.project | 1 + .../.project | 1 + java/com.sap.sailing.domain.swisstimingreplayadapter/.project | 1 + java/com.sap.sailing.domain.test/.project | 1 + java/com.sap.sailing.domain.tractracadapter.persistence/.project | 1 + java/com.sap.sailing.domain.tractracadapter/.project | 1 + java/com.sap.sailing.domain.windfinderadapter.test/.project | 1 + java/com.sap.sailing.domain.windfinderadapter/.project | 1 + java/com.sap.sailing.domain/.project | 1 + java/com.sap.sailing.ess40.resultimport.test/.project | 1 + java/com.sap.sailing.ess40.resultimport/.project | 1 + java/com.sap.sailing.expeditionconnector.common/.project | 1 + java/com.sap.sailing.expeditionconnector.persistence/.project | 1 + java/com.sap.sailing.expeditionconnector.test/.project | 1 + java/com.sap.sailing.expeditionconnector/.project | 1 + java/com.sap.sailing.feature.p2build/.project | 1 + java/com.sap.sailing.feature.runtime/.project | 1 + java/com.sap.sailing.feature/.project | 1 + java/com.sap.sailing.freg.resultimport.test/.project | 1 + java/com.sap.sailing.freg.resultimport/.project | 1 + java/com.sap.sailing.geocoding.test/.project | 1 + java/com.sap.sailing.geocoding/.project | 1 + java/com.sap.sailing.grib.test/.project | 1 + java/com.sap.sailing.grib/.project | 1 + java/com.sap.sailing.gwt.ui/.project | 1 + java/com.sap.sailing.hanaexport/.project | 1 + java/com.sap.sailing.ingestion/.project | 1 + java/com.sap.sailing.kiworesultimport.test/.project | 1 + java/com.sap.sailing.kiworesultimport/.project | 1 + java/com.sap.sailing.landscape.common/.project | 1 + java/com.sap.sailing.landscape.test/.project | 1 + java/com.sap.sailing.landscape.ui/.project | 1 + java/com.sap.sailing.landscape/.project | 1 + java/com.sap.sailing.manage2sail.resultimport.test/.project | 1 + java/com.sap.sailing.manage2sail.resultimport/.project | 1 + java/com.sap.sailing.manage2sail/.project | 1 + java/com.sap.sailing.media.persistence.test/.project | 1 + java/com.sap.sailing.mongodb.test/.project | 1 + java/com.sap.sailing.monitoring/.project | 1 + java/com.sap.sailing.news/.project | 1 + java/com.sap.sailing.nmeaconnector.test/.project | 1 + java/com.sap.sailing.nmeaconnector/.project | 1 + java/com.sap.sailing.polars.datamining.shared/.project | 1 + java/com.sap.sailing.polars.datamining/.project | 1 + java/com.sap.sailing.polars.test/.project | 1 + java/com.sap.sailing.polars/.project | 1 + java/com.sap.sailing.resultimport/.project | 1 + java/com.sap.sailing.routeconverterjava11extension/.project | 1 + java/com.sap.sailing.sailwave.resultimport.test/.project | 1 + java/com.sap.sailing.sailwave.resultimport/.project | 1 + java/com.sap.sailing.selenium.test/.project | 1 + .../.project | 1 + java/com.sap.sailing.server.gateway.serialization.test/.project | 1 + java/com.sap.sailing.server.gateway.serialization/.project | 1 + java/com.sap.sailing.server.gateway.test.support/.project | 1 + java/com.sap.sailing.server.gateway.test/.project | 1 + java/com.sap.sailing.server.gateway/.project | 1 + java/com.sap.sailing.server.interface/.project | 1 + java/com.sap.sailing.server.replication.test/.project | 1 + java/com.sap.sailing.server.test/.project | 1 + java/com.sap.sailing.server.testsupport/.project | 1 + java/com.sap.sailing.server.trackfiles.test/.project | 1 + java/com.sap.sailing.server.trackfiles/.project | 1 + java/com.sap.sailing.server/.project | 1 + java/com.sap.sailing.shared.persistence/.project | 1 + java/com.sap.sailing.shared.server.gateway/.project | 1 + java/com.sap.sailing.shared.server/.project | 1 + java/com.sap.sailing.simulator.test/.project | 1 + java/com.sap.sailing.simulator/.project | 1 + java/com.sap.sailing.targetplatform.base/.project | 1 + java/com.sap.sailing.targetplatform.updater/.project | 1 + java/com.sap.sailing.targetplatform/.project | 1 + java/com.sap.sailing.udpconnector/.project | 1 + java/com.sap.sailing.velum.resultimport.test/.project | 1 + java/com.sap.sailing.velum.resultimport/.project | 1 + java/com.sap.sailing.windestimation.lab/.project | 1 + java/com.sap.sailing.windestimation.test/.project | 1 + java/com.sap.sailing.windestimation/.project | 1 + java/com.sap.sailing.www/.project | 1 + java/com.sap.sailing.xmlexport.test/.project | 1 + java/com.sap.sailing.xmlexport/.project | 1 + java/com.sap.sailing.xrr.resultimport.test/.project | 1 + java/com.sap.sailing.xrr.resultimport/.project | 1 + java/com.sap.sailing.xrr.schema/.project | 1 + java/com.sap.sailing.xrr.structureimport/.project | 1 + java/com.sap.sailing.yachtscoring.resultimport.test/.project | 1 + java/com.sap.sailing.yachtscoring.resultimport/.project | 1 + java/com.sap.sse.common.test/.project | 1 + java/com.sap.sse.common/.project | 1 + java/com.sap.sse.datamining.annotations/.project | 1 + java/com.sap.sse.datamining.shared/.project | 1 + java/com.sap.sse.datamining.test/.project | 1 + java/com.sap.sse.datamining.ui.test/.project | 1 + java/com.sap.sse.datamining.ui/.project | 1 + java/com.sap.sse.datamining/.project | 1 + java/com.sap.sse.debranding/.project | 1 + java/com.sap.sse.feature.runtime/.project | 1 + java/com.sap.sse.feature/.project | 1 + java/com.sap.sse.filestorage.test/.project | 1 + java/com.sap.sse.filestorage/.project | 1 + java/com.sap.sse.gwt.adminconsole/.project | 1 + java/com.sap.sse.gwt.test/.project | 1 + java/com.sap.sse.gwt/.project | 1 + java/com.sap.sse.jersey.jaxbdependencyfragment/.project | 1 + java/com.sap.sse.landscape.aws.common/.project | 1 + java/com.sap.sse.landscape.aws.persistence/.project | 1 + java/com.sap.sse.landscape.aws.test/.project | 1 + java/com.sap.sse.landscape.aws/.project | 1 + java/com.sap.sse.landscape.common/.project | 1 + java/com.sap.sse.landscape/.project | 1 + java/com.sap.sse.mail.replication.test/.project | 1 + java/com.sap.sse.mail.replication.testsupport/.project | 1 + java/com.sap.sse.mail/.project | 1 + java/com.sap.sse.mongodb/.project | 1 + java/com.sap.sse.operationaltransformation.test/.project | 1 + java/com.sap.sse.operationaltransformation/.project | 1 + java/com.sap.sse.replication.interfaces/.project | 1 + java/com.sap.sse.replication.persistence/.project | 1 + java/com.sap.sse.replication.testsupport/.project | 1 + java/com.sap.sse.replication/.project | 1 + java/com.sap.sse.security.common/.project | 1 + java/com.sap.sse.security.interface/.project | 1 + java/com.sap.sse.security.persistence.test/.project | 1 + java/com.sap.sse.security.persistence/.project | 1 + java/com.sap.sse.security.replication.test/.project | 1 + java/com.sap.sse.security.storemerging.test/.project | 1 + java/com.sap.sse.security.storemerging/.project | 1 + java/com.sap.sse.security.test/.project | 1 + java/com.sap.sse.security.testsupport/.project | 1 + java/com.sap.sse.security.ui/.project | 1 + java/com.sap.sse.security.userstore.mongodb/.project | 1 + java/com.sap.sse.security/.project | 1 + java/com.sap.sse.shared.android.test/.project | 1 + java/com.sap.sse.shared.android/.project | 1 + java/com.sap.sse.test/.project | 1 + java/com.sap.sse.threadmanager/.project | 1 + java/com.sap.sse/.project | 1 + java/com.tractrac.clientmodule/.project | 1 + java/elemental2/.project | 1 + java/net.sf.marineapi.test/.project | 1 + java/net.sf.marineapi/.project | 1 + java/org.json.simple/.project | 1 + java/org.moxieapps.gwt.highcharts/.project | 1 + java/org.mp4parser.isoparser/.project | 1 + java/org.openqa.selenium.osgi/.project | 1 + mobile/.project | 1 + wiki/.project | 1 + 187 files changed, 187 insertions(+) diff --git a/.project b/.project index 0bf7812c78a..858a81a4f2f 100644 --- a/.project +++ b/.project @@ -1,4 +1,5 @@ + sailing diff --git a/ios/.project b/ios/.project index b3198ce6197..9fec7f6c9d2 100644 --- a/ios/.project +++ b/ios/.project @@ -1,4 +1,5 @@ + ios diff --git a/java/.project b/java/.project index 2c17f2fa456..2688d81f0cd 100644 --- a/java/.project +++ b/java/.project @@ -1,4 +1,5 @@ + java diff --git a/java/com.amazon.aws.aws-java-api.updatesite/.project b/java/com.amazon.aws.aws-java-api.updatesite/.project index 5fc4924c41a..c7675ea5d40 100755 --- a/java/com.amazon.aws.aws-java-api.updatesite/.project +++ b/java/com.amazon.aws.aws-java-api.updatesite/.project @@ -1,4 +1,5 @@ + com.amazon.aws.aws-java-api.updatesite diff --git a/java/com.github.branflake2267.gwt-maps-api/.project b/java/com.github.branflake2267.gwt-maps-api/.project index 5447b2c01ac..6e532e72d28 100644 --- a/java/com.github.branflake2267.gwt-maps-api/.project +++ b/java/com.github.branflake2267.gwt-maps-api/.project @@ -1,4 +1,5 @@ + com.github.branflake2267.gwt-maps-api diff --git a/java/com.google.gwt.ajaxloader/.project b/java/com.google.gwt.ajaxloader/.project index 29bd272350b..a0c6e4c7b25 100644 --- a/java/com.google.gwt.ajaxloader/.project +++ b/java/com.google.gwt.ajaxloader/.project @@ -1,4 +1,5 @@ + com.google.gwt.ajaxloader diff --git a/java/com.google.gwt.dev/.project b/java/com.google.gwt.dev/.project index 07fec1d03ba..922d66357b3 100644 --- a/java/com.google.gwt.dev/.project +++ b/java/com.google.gwt.dev/.project @@ -1,4 +1,5 @@ + com.google.gwt.dev diff --git a/java/com.google.gwt.servlet/.project b/java/com.google.gwt.servlet/.project index 4019bf222f8..45a23a8c5ad 100644 --- a/java/com.google.gwt.servlet/.project +++ b/java/com.google.gwt.servlet/.project @@ -1,4 +1,5 @@ + com.google.gwt.servlet diff --git a/java/com.google.gwt.user/.project b/java/com.google.gwt.user/.project index 5a611228710..c71b0c775f9 100644 --- a/java/com.google.gwt.user/.project +++ b/java/com.google.gwt.user/.project @@ -1,4 +1,5 @@ + com.google.gwt.user diff --git a/java/com.googlecode.java-diff-utils.test/.project b/java/com.googlecode.java-diff-utils.test/.project index 30644bdeb0a..1a0b01fdfe0 100755 --- a/java/com.googlecode.java-diff-utils.test/.project +++ b/java/com.googlecode.java-diff-utils.test/.project @@ -1,4 +1,5 @@ + com.googlecode.java-diff-utils.test diff --git a/java/com.googlecode.java-diff-utils/.project b/java/com.googlecode.java-diff-utils/.project index 8da7abc7b55..6d2d0074f5a 100755 --- a/java/com.googlecode.java-diff-utils/.project +++ b/java/com.googlecode.java-diff-utils/.project @@ -1,4 +1,5 @@ + com.googlecode.java-diff-utils diff --git a/java/com.googlecode.mgwt/.project b/java/com.googlecode.mgwt/.project index 64cd0a55b18..3db209b0d2d 100644 --- a/java/com.googlecode.mgwt/.project +++ b/java/com.googlecode.mgwt/.project @@ -1,4 +1,5 @@ + com.googlecode.mgwt diff --git a/java/com.sap.sailing.barbados.resultimport.test/.project b/java/com.sap.sailing.barbados.resultimport.test/.project index 000b089d063..df38c51bdc1 100644 --- a/java/com.sap.sailing.barbados.resultimport.test/.project +++ b/java/com.sap.sailing.barbados.resultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.barbados.resultimport.test diff --git a/java/com.sap.sailing.barbados.resultimport/.project b/java/com.sap.sailing.barbados.resultimport/.project index 82a6c202035..797fd9f5222 100644 --- a/java/com.sap.sailing.barbados.resultimport/.project +++ b/java/com.sap.sailing.barbados.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.barbados.resultimport diff --git a/java/com.sap.sailing.competitorimport/.project b/java/com.sap.sailing.competitorimport/.project index 4371e50fca6..0fbc3edb87c 100644 --- a/java/com.sap.sailing.competitorimport/.project +++ b/java/com.sap.sailing.competitorimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.competitorimport diff --git a/java/com.sap.sailing.dashboards.gwt/.project b/java/com.sap.sailing.dashboards.gwt/.project index 520fc2bd59d..ba40735d21d 100644 --- a/java/com.sap.sailing.dashboards.gwt/.project +++ b/java/com.sap.sailing.dashboards.gwt/.project @@ -1,4 +1,5 @@ + com.sap.sailing.dashboards.gwt diff --git a/java/com.sap.sailing.datamining.provider/.project b/java/com.sap.sailing.datamining.provider/.project index 13699a1a554..b640c43a088 100755 --- a/java/com.sap.sailing.datamining.provider/.project +++ b/java/com.sap.sailing.datamining.provider/.project @@ -1,4 +1,5 @@ + com.sap.sailing.datamining.provider diff --git a/java/com.sap.sailing.datamining.shared/.project b/java/com.sap.sailing.datamining.shared/.project index 3e76c7f629f..2a7d4c31ce6 100644 --- a/java/com.sap.sailing.datamining.shared/.project +++ b/java/com.sap.sailing.datamining.shared/.project @@ -1,4 +1,5 @@ + com.sap.sailing.datamining.shared diff --git a/java/com.sap.sailing.datamining.test/.project b/java/com.sap.sailing.datamining.test/.project index 91f4fe867cc..145d3978960 100644 --- a/java/com.sap.sailing.datamining.test/.project +++ b/java/com.sap.sailing.datamining.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.datamining.test diff --git a/java/com.sap.sailing.datamining/.project b/java/com.sap.sailing.datamining/.project index f40554ff5f4..4ca3b844804 100644 --- a/java/com.sap.sailing.datamining/.project +++ b/java/com.sap.sailing.datamining/.project @@ -1,4 +1,5 @@ + com.sap.sailing.datamining diff --git a/java/com.sap.sailing.declination.test/.project b/java/com.sap.sailing.declination.test/.project index 58850df91c8..8a9c1c5ac22 100755 --- a/java/com.sap.sailing.declination.test/.project +++ b/java/com.sap.sailing.declination.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.declination.test diff --git a/java/com.sap.sailing.declination/.project b/java/com.sap.sailing.declination/.project index d4ad11c6d72..833f2da6eba 100755 --- a/java/com.sap.sailing.declination/.project +++ b/java/com.sap.sailing.declination/.project @@ -1,4 +1,5 @@ + com.sap.sailing.declination diff --git a/java/com.sap.sailing.domain.bravoadapter/.project b/java/com.sap.sailing.domain.bravoadapter/.project index b9978a06950..69b1271a9cf 100755 --- a/java/com.sap.sailing.domain.bravoadapter/.project +++ b/java/com.sap.sailing.domain.bravoadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.bravoadapter diff --git a/java/com.sap.sailing.domain.common/.project b/java/com.sap.sailing.domain.common/.project index 7779c8b14a0..37b0b350392 100755 --- a/java/com.sap.sailing.domain.common/.project +++ b/java/com.sap.sailing.domain.common/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.common diff --git a/java/com.sap.sailing.domain.deckmanadapter.test/.project b/java/com.sap.sailing.domain.deckmanadapter.test/.project index fd1b6107082..9ad85fe6ed9 100755 --- a/java/com.sap.sailing.domain.deckmanadapter.test/.project +++ b/java/com.sap.sailing.domain.deckmanadapter.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.deckmanadapter.test diff --git a/java/com.sap.sailing.domain.deckmanadapter/.project b/java/com.sap.sailing.domain.deckmanadapter/.project index 93bbb09883e..3a16e43f272 100755 --- a/java/com.sap.sailing.domain.deckmanadapter/.project +++ b/java/com.sap.sailing.domain.deckmanadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.deckmanadapter diff --git a/java/com.sap.sailing.domain.expeditionadapter/.project b/java/com.sap.sailing.domain.expeditionadapter/.project index a413084a0cd..eb050b15eee 100755 --- a/java/com.sap.sailing.domain.expeditionadapter/.project +++ b/java/com.sap.sailing.domain.expeditionadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.expeditionadapter diff --git a/java/com.sap.sailing.domain.igtimiadapter.gateway/.project b/java/com.sap.sailing.domain.igtimiadapter.gateway/.project index fb064dabaca..e1a1852eac7 100644 --- a/java/com.sap.sailing.domain.igtimiadapter.gateway/.project +++ b/java/com.sap.sailing.domain.igtimiadapter.gateway/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.igtimiadapter.gateway diff --git a/java/com.sap.sailing.domain.igtimiadapter.persistence/.project b/java/com.sap.sailing.domain.igtimiadapter.persistence/.project index 7d52cb74c97..e1d04ba8e78 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.persistence/.project +++ b/java/com.sap.sailing.domain.igtimiadapter.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.igtimiadapter.persistence diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/.project b/java/com.sap.sailing.domain.igtimiadapter.test/.project index 4ec7f1c59af..364aa20e006 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/.project +++ b/java/com.sap.sailing.domain.igtimiadapter.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.igtimiadapter.test diff --git a/java/com.sap.sailing.domain.igtimiadapter/.project b/java/com.sap.sailing.domain.igtimiadapter/.project index 97dd7581430..e849151ee12 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/.project +++ b/java/com.sap.sailing.domain.igtimiadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.igtimiadapter diff --git a/java/com.sap.sailing.domain.oceanraceadapter/.project b/java/com.sap.sailing.domain.oceanraceadapter/.project index 3eb259d97b1..b055a77087e 100755 --- a/java/com.sap.sailing.domain.oceanraceadapter/.project +++ b/java/com.sap.sailing.domain.oceanraceadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.oceanraceadapter diff --git a/java/com.sap.sailing.domain.persistence/.project b/java/com.sap.sailing.domain.persistence/.project index 63ad26fbdd7..dc3bb3ad6c8 100755 --- a/java/com.sap.sailing.domain.persistence/.project +++ b/java/com.sap.sailing.domain.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.persistence diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/.project b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.project index 30f37c10090..644b5855ac0 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/.project +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.racelogtrackingadapter.test diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.project b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.project index f3c0beddc15..a6a0c2fea84 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.project +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.racelogtrackingadapter.testsupport diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.project b/java/com.sap.sailing.domain.racelogtrackingadapter/.project index 6d8ea47c43e..188364a5e6a 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/.project +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.racelogtrackingadapter diff --git a/java/com.sap.sailing.domain.shared.android.test/.project b/java/com.sap.sailing.domain.shared.android.test/.project index b20b2f876c0..9afa7b2d98b 100644 --- a/java/com.sap.sailing.domain.shared.android.test/.project +++ b/java/com.sap.sailing.domain.shared.android.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.shared.android.test diff --git a/java/com.sap.sailing.domain.shared.android/.project b/java/com.sap.sailing.domain.shared.android/.project index eba6e1dade9..8210775f709 100644 --- a/java/com.sap.sailing.domain.shared.android/.project +++ b/java/com.sap.sailing.domain.shared.android/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.shared.android diff --git a/java/com.sap.sailing.domain.swisstimingadapter.persistence/.project b/java/com.sap.sailing.domain.swisstimingadapter.persistence/.project index b059e9ad7ed..1e529e6cb3d 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.persistence/.project +++ b/java/com.sap.sailing.domain.swisstimingadapter.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.swisstimingadapter.persistence diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/.project b/java/com.sap.sailing.domain.swisstimingadapter.test/.project index 0310bcf9121..3068871d47c 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/.project +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.swisstimingadapter.test diff --git a/java/com.sap.sailing.domain.swisstimingadapter/.project b/java/com.sap.sailing.domain.swisstimingadapter/.project index a087e0f7357..28ae243b99f 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/.project +++ b/java/com.sap.sailing.domain.swisstimingadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.swisstimingadapter diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/.project b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/.project index 8bc1b12493d..6f4eb0aa5e4 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/.project +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.swisstimingreplayadapter.test diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/.project b/java/com.sap.sailing.domain.swisstimingreplayadapter/.project index 1d9da58cdab..79860e000a2 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/.project +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.swisstimingreplayadapter diff --git a/java/com.sap.sailing.domain.test/.project b/java/com.sap.sailing.domain.test/.project index 0a9550a016b..eaa66d762a8 100755 --- a/java/com.sap.sailing.domain.test/.project +++ b/java/com.sap.sailing.domain.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.test diff --git a/java/com.sap.sailing.domain.tractracadapter.persistence/.project b/java/com.sap.sailing.domain.tractracadapter.persistence/.project index 343c04d4a85..8ff13ac0c14 100755 --- a/java/com.sap.sailing.domain.tractracadapter.persistence/.project +++ b/java/com.sap.sailing.domain.tractracadapter.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.tractracadapter.persistence diff --git a/java/com.sap.sailing.domain.tractracadapter/.project b/java/com.sap.sailing.domain.tractracadapter/.project index f8cd2e2d499..336869404b0 100755 --- a/java/com.sap.sailing.domain.tractracadapter/.project +++ b/java/com.sap.sailing.domain.tractracadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.tractracadapter diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/.project b/java/com.sap.sailing.domain.windfinderadapter.test/.project index f4e90b744ba..11a6d4c19ed 100644 --- a/java/com.sap.sailing.domain.windfinderadapter.test/.project +++ b/java/com.sap.sailing.domain.windfinderadapter.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.windfinderadapter.test diff --git a/java/com.sap.sailing.domain.windfinderadapter/.project b/java/com.sap.sailing.domain.windfinderadapter/.project index 8efe69d0cbe..85d11e3b1e5 100644 --- a/java/com.sap.sailing.domain.windfinderadapter/.project +++ b/java/com.sap.sailing.domain.windfinderadapter/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain.windfinderadapter diff --git a/java/com.sap.sailing.domain/.project b/java/com.sap.sailing.domain/.project index 667ec7791d2..3d26aded333 100755 --- a/java/com.sap.sailing.domain/.project +++ b/java/com.sap.sailing.domain/.project @@ -1,4 +1,5 @@ + com.sap.sailing.domain diff --git a/java/com.sap.sailing.ess40.resultimport.test/.project b/java/com.sap.sailing.ess40.resultimport.test/.project index bacaddea80a..f90cd8c5df2 100755 --- a/java/com.sap.sailing.ess40.resultimport.test/.project +++ b/java/com.sap.sailing.ess40.resultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.ess40.resultimport.test diff --git a/java/com.sap.sailing.ess40.resultimport/.project b/java/com.sap.sailing.ess40.resultimport/.project index b665aeee3fb..19b57226060 100755 --- a/java/com.sap.sailing.ess40.resultimport/.project +++ b/java/com.sap.sailing.ess40.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.ess40.resultimport diff --git a/java/com.sap.sailing.expeditionconnector.common/.project b/java/com.sap.sailing.expeditionconnector.common/.project index 255447215e5..14861f1eaf4 100755 --- a/java/com.sap.sailing.expeditionconnector.common/.project +++ b/java/com.sap.sailing.expeditionconnector.common/.project @@ -1,4 +1,5 @@ + com.sap.sailing.expeditionconnector.common diff --git a/java/com.sap.sailing.expeditionconnector.persistence/.project b/java/com.sap.sailing.expeditionconnector.persistence/.project index c6034e7c533..85f0cd943f8 100755 --- a/java/com.sap.sailing.expeditionconnector.persistence/.project +++ b/java/com.sap.sailing.expeditionconnector.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sailing.expeditionconnector.persistence diff --git a/java/com.sap.sailing.expeditionconnector.test/.project b/java/com.sap.sailing.expeditionconnector.test/.project index cb065b0d8fb..26be474eb6a 100755 --- a/java/com.sap.sailing.expeditionconnector.test/.project +++ b/java/com.sap.sailing.expeditionconnector.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.expeditionconnector.test diff --git a/java/com.sap.sailing.expeditionconnector/.project b/java/com.sap.sailing.expeditionconnector/.project index 1c71354f3f4..ffb9a496b21 100755 --- a/java/com.sap.sailing.expeditionconnector/.project +++ b/java/com.sap.sailing.expeditionconnector/.project @@ -1,4 +1,5 @@ + com.sap.sailing.expeditionconnector diff --git a/java/com.sap.sailing.feature.p2build/.project b/java/com.sap.sailing.feature.p2build/.project index 943143b415b..a34343adb09 100644 --- a/java/com.sap.sailing.feature.p2build/.project +++ b/java/com.sap.sailing.feature.p2build/.project @@ -1,4 +1,5 @@ + com.sap.sailing.feature.p2build diff --git a/java/com.sap.sailing.feature.runtime/.project b/java/com.sap.sailing.feature.runtime/.project index 30cc8faa054..5e72dbc5835 100644 --- a/java/com.sap.sailing.feature.runtime/.project +++ b/java/com.sap.sailing.feature.runtime/.project @@ -1,4 +1,5 @@ + com.sap.sailing.feature.runtime diff --git a/java/com.sap.sailing.feature/.project b/java/com.sap.sailing.feature/.project index 68384dadae7..fd6fffaac0f 100644 --- a/java/com.sap.sailing.feature/.project +++ b/java/com.sap.sailing.feature/.project @@ -1,4 +1,5 @@ + com.sap.sailing.feature diff --git a/java/com.sap.sailing.freg.resultimport.test/.project b/java/com.sap.sailing.freg.resultimport.test/.project index 87d00f1126b..802317fa983 100755 --- a/java/com.sap.sailing.freg.resultimport.test/.project +++ b/java/com.sap.sailing.freg.resultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.freg.resultimport.test diff --git a/java/com.sap.sailing.freg.resultimport/.project b/java/com.sap.sailing.freg.resultimport/.project index a99382aeabe..717f4d59921 100755 --- a/java/com.sap.sailing.freg.resultimport/.project +++ b/java/com.sap.sailing.freg.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.freg.resultimport diff --git a/java/com.sap.sailing.geocoding.test/.project b/java/com.sap.sailing.geocoding.test/.project index ccee6e7ad13..f120212cb7f 100644 --- a/java/com.sap.sailing.geocoding.test/.project +++ b/java/com.sap.sailing.geocoding.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.geocoding.test diff --git a/java/com.sap.sailing.geocoding/.project b/java/com.sap.sailing.geocoding/.project index 18680f3dc59..43fa5d8e481 100644 --- a/java/com.sap.sailing.geocoding/.project +++ b/java/com.sap.sailing.geocoding/.project @@ -1,4 +1,5 @@ + com.sap.sailing.geocoding diff --git a/java/com.sap.sailing.grib.test/.project b/java/com.sap.sailing.grib.test/.project index 9bc15e55f91..0dfbf9157be 100644 --- a/java/com.sap.sailing.grib.test/.project +++ b/java/com.sap.sailing.grib.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.grib.test diff --git a/java/com.sap.sailing.grib/.project b/java/com.sap.sailing.grib/.project index d0428178f93..4f96248590c 100644 --- a/java/com.sap.sailing.grib/.project +++ b/java/com.sap.sailing.grib/.project @@ -1,4 +1,5 @@ + com.sap.sailing.grib diff --git a/java/com.sap.sailing.gwt.ui/.project b/java/com.sap.sailing.gwt.ui/.project index c684e289776..b68e1b53b99 100755 --- a/java/com.sap.sailing.gwt.ui/.project +++ b/java/com.sap.sailing.gwt.ui/.project @@ -1,4 +1,5 @@ + com.sap.sailing.gwt.ui diff --git a/java/com.sap.sailing.hanaexport/.project b/java/com.sap.sailing.hanaexport/.project index 42a4d249d41..c459416bdaa 100755 --- a/java/com.sap.sailing.hanaexport/.project +++ b/java/com.sap.sailing.hanaexport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.hanaexport diff --git a/java/com.sap.sailing.ingestion/.project b/java/com.sap.sailing.ingestion/.project index be841e46bd2..983d9700339 100755 --- a/java/com.sap.sailing.ingestion/.project +++ b/java/com.sap.sailing.ingestion/.project @@ -1,4 +1,5 @@ + com.sap.sailing.ingestion diff --git a/java/com.sap.sailing.kiworesultimport.test/.project b/java/com.sap.sailing.kiworesultimport.test/.project index 1421921fba3..71e8db2bf58 100644 --- a/java/com.sap.sailing.kiworesultimport.test/.project +++ b/java/com.sap.sailing.kiworesultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.kiworesultimport.test diff --git a/java/com.sap.sailing.kiworesultimport/.project b/java/com.sap.sailing.kiworesultimport/.project index a1aa50371ad..9c8633f8457 100644 --- a/java/com.sap.sailing.kiworesultimport/.project +++ b/java/com.sap.sailing.kiworesultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.kiworesultimport diff --git a/java/com.sap.sailing.landscape.common/.project b/java/com.sap.sailing.landscape.common/.project index 1bc63554b88..070e5e7f4ef 100755 --- a/java/com.sap.sailing.landscape.common/.project +++ b/java/com.sap.sailing.landscape.common/.project @@ -1,4 +1,5 @@ + com.sap.sailing.landscape.common diff --git a/java/com.sap.sailing.landscape.test/.project b/java/com.sap.sailing.landscape.test/.project index 1f13eb0413e..ffa5c700e6c 100755 --- a/java/com.sap.sailing.landscape.test/.project +++ b/java/com.sap.sailing.landscape.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.landscape.test diff --git a/java/com.sap.sailing.landscape.ui/.project b/java/com.sap.sailing.landscape.ui/.project index c35b99b761f..936806c8067 100755 --- a/java/com.sap.sailing.landscape.ui/.project +++ b/java/com.sap.sailing.landscape.ui/.project @@ -1,4 +1,5 @@ + com.sap.sailing.landscape.ui diff --git a/java/com.sap.sailing.landscape/.project b/java/com.sap.sailing.landscape/.project index ccb496f8ed3..57b3e9455eb 100755 --- a/java/com.sap.sailing.landscape/.project +++ b/java/com.sap.sailing.landscape/.project @@ -1,4 +1,5 @@ + com.sap.sailing.landscape diff --git a/java/com.sap.sailing.manage2sail.resultimport.test/.project b/java/com.sap.sailing.manage2sail.resultimport.test/.project index 8f4948ee364..7ea45394538 100644 --- a/java/com.sap.sailing.manage2sail.resultimport.test/.project +++ b/java/com.sap.sailing.manage2sail.resultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.manage2sail.resultimport.test diff --git a/java/com.sap.sailing.manage2sail.resultimport/.project b/java/com.sap.sailing.manage2sail.resultimport/.project index a2606335bd9..f4da9c117b5 100644 --- a/java/com.sap.sailing.manage2sail.resultimport/.project +++ b/java/com.sap.sailing.manage2sail.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.manage2sail.resultimport diff --git a/java/com.sap.sailing.manage2sail/.project b/java/com.sap.sailing.manage2sail/.project index ca7605b13f9..0859fc9bf28 100644 --- a/java/com.sap.sailing.manage2sail/.project +++ b/java/com.sap.sailing.manage2sail/.project @@ -1,4 +1,5 @@ + com.sap.sailing.manage2sail diff --git a/java/com.sap.sailing.media.persistence.test/.project b/java/com.sap.sailing.media.persistence.test/.project index ab02faa2b7c..b62d9240654 100644 --- a/java/com.sap.sailing.media.persistence.test/.project +++ b/java/com.sap.sailing.media.persistence.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.media.persistence.test diff --git a/java/com.sap.sailing.mongodb.test/.project b/java/com.sap.sailing.mongodb.test/.project index a4a000d741a..14487a370dc 100755 --- a/java/com.sap.sailing.mongodb.test/.project +++ b/java/com.sap.sailing.mongodb.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.mongodb.test diff --git a/java/com.sap.sailing.monitoring/.project b/java/com.sap.sailing.monitoring/.project index 5bf2c3ba6d0..bc2d14d1764 100644 --- a/java/com.sap.sailing.monitoring/.project +++ b/java/com.sap.sailing.monitoring/.project @@ -1,4 +1,5 @@ + com.sap.sailing.monitoring diff --git a/java/com.sap.sailing.news/.project b/java/com.sap.sailing.news/.project index cba19cda41d..4aec8511bd0 100644 --- a/java/com.sap.sailing.news/.project +++ b/java/com.sap.sailing.news/.project @@ -1,4 +1,5 @@ + com.sap.sailing.news diff --git a/java/com.sap.sailing.nmeaconnector.test/.project b/java/com.sap.sailing.nmeaconnector.test/.project index c521410104f..6cd2aefca1b 100755 --- a/java/com.sap.sailing.nmeaconnector.test/.project +++ b/java/com.sap.sailing.nmeaconnector.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.nmeaconnector.test diff --git a/java/com.sap.sailing.nmeaconnector/.project b/java/com.sap.sailing.nmeaconnector/.project index c8d00592061..c1b0ba936e1 100755 --- a/java/com.sap.sailing.nmeaconnector/.project +++ b/java/com.sap.sailing.nmeaconnector/.project @@ -1,4 +1,5 @@ + com.sap.sailing.nmeaconnector diff --git a/java/com.sap.sailing.polars.datamining.shared/.project b/java/com.sap.sailing.polars.datamining.shared/.project index 6dc505ab246..89ec4bfa575 100644 --- a/java/com.sap.sailing.polars.datamining.shared/.project +++ b/java/com.sap.sailing.polars.datamining.shared/.project @@ -1,4 +1,5 @@ + com.sap.sailing.polars.datamining.shared diff --git a/java/com.sap.sailing.polars.datamining/.project b/java/com.sap.sailing.polars.datamining/.project index a217a17527e..8f76bc03bdb 100644 --- a/java/com.sap.sailing.polars.datamining/.project +++ b/java/com.sap.sailing.polars.datamining/.project @@ -1,4 +1,5 @@ + com.sap.sailing.polars.datamining diff --git a/java/com.sap.sailing.polars.test/.project b/java/com.sap.sailing.polars.test/.project index 7980f4925e6..4b7666f51ee 100644 --- a/java/com.sap.sailing.polars.test/.project +++ b/java/com.sap.sailing.polars.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.polars.test diff --git a/java/com.sap.sailing.polars/.project b/java/com.sap.sailing.polars/.project index b2e142dedfa..30271312397 100644 --- a/java/com.sap.sailing.polars/.project +++ b/java/com.sap.sailing.polars/.project @@ -1,4 +1,5 @@ + com.sap.sailing.polars diff --git a/java/com.sap.sailing.resultimport/.project b/java/com.sap.sailing.resultimport/.project index f36785ea507..097e5b95ef4 100644 --- a/java/com.sap.sailing.resultimport/.project +++ b/java/com.sap.sailing.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.resultimport diff --git a/java/com.sap.sailing.routeconverterjava11extension/.project b/java/com.sap.sailing.routeconverterjava11extension/.project index 6fc2819bb00..20f0bb07b8e 100755 --- a/java/com.sap.sailing.routeconverterjava11extension/.project +++ b/java/com.sap.sailing.routeconverterjava11extension/.project @@ -1,4 +1,5 @@ + com.sap.sailing.routeconverterjava11extension diff --git a/java/com.sap.sailing.sailwave.resultimport.test/.project b/java/com.sap.sailing.sailwave.resultimport.test/.project index 818efda9dc5..d8533ca8fa2 100644 --- a/java/com.sap.sailing.sailwave.resultimport.test/.project +++ b/java/com.sap.sailing.sailwave.resultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.sailwave.resultimport.test diff --git a/java/com.sap.sailing.sailwave.resultimport/.project b/java/com.sap.sailing.sailwave.resultimport/.project index 598a39f33d8..cd6c9161524 100644 --- a/java/com.sap.sailing.sailwave.resultimport/.project +++ b/java/com.sap.sailing.sailwave.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.sailwave.resultimport diff --git a/java/com.sap.sailing.selenium.test/.project b/java/com.sap.sailing.selenium.test/.project index 6d980ffd37d..e3e1bdc244c 100644 --- a/java/com.sap.sailing.selenium.test/.project +++ b/java/com.sap.sailing.selenium.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.selenium.test diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/.project b/java/com.sap.sailing.server.gateway.serialization.shared.android/.project index 0802e21577c..eb43e98cef7 100755 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/.project +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.gateway.serialization.shared.android diff --git a/java/com.sap.sailing.server.gateway.serialization.test/.project b/java/com.sap.sailing.server.gateway.serialization.test/.project index 8fd93b5b758..5ceb8b559ec 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/.project +++ b/java/com.sap.sailing.server.gateway.serialization.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.gateway.serialization.test diff --git a/java/com.sap.sailing.server.gateway.serialization/.project b/java/com.sap.sailing.server.gateway.serialization/.project index cc12d1eed4a..36f8261f509 100644 --- a/java/com.sap.sailing.server.gateway.serialization/.project +++ b/java/com.sap.sailing.server.gateway.serialization/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.gateway.serialization diff --git a/java/com.sap.sailing.server.gateway.test.support/.project b/java/com.sap.sailing.server.gateway.test.support/.project index edc0549576d..ae230b38129 100644 --- a/java/com.sap.sailing.server.gateway.test.support/.project +++ b/java/com.sap.sailing.server.gateway.test.support/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.gateway.test.support diff --git a/java/com.sap.sailing.server.gateway.test/.project b/java/com.sap.sailing.server.gateway.test/.project index 4cf34e53fa2..025bb810dc1 100644 --- a/java/com.sap.sailing.server.gateway.test/.project +++ b/java/com.sap.sailing.server.gateway.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.gateway.test diff --git a/java/com.sap.sailing.server.gateway/.project b/java/com.sap.sailing.server.gateway/.project index d04cc8ee9a6..eaf683d4ed9 100644 --- a/java/com.sap.sailing.server.gateway/.project +++ b/java/com.sap.sailing.server.gateway/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.gateway diff --git a/java/com.sap.sailing.server.interface/.project b/java/com.sap.sailing.server.interface/.project index 8f602d997ba..1ef38327e23 100755 --- a/java/com.sap.sailing.server.interface/.project +++ b/java/com.sap.sailing.server.interface/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.interface diff --git a/java/com.sap.sailing.server.replication.test/.project b/java/com.sap.sailing.server.replication.test/.project index 6fb67f82008..e1a4dedf81b 100644 --- a/java/com.sap.sailing.server.replication.test/.project +++ b/java/com.sap.sailing.server.replication.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.replication.test diff --git a/java/com.sap.sailing.server.test/.project b/java/com.sap.sailing.server.test/.project index 7c0a92e867a..1e00780e86d 100755 --- a/java/com.sap.sailing.server.test/.project +++ b/java/com.sap.sailing.server.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.test diff --git a/java/com.sap.sailing.server.testsupport/.project b/java/com.sap.sailing.server.testsupport/.project index fc826dcca0d..113bbf6d031 100644 --- a/java/com.sap.sailing.server.testsupport/.project +++ b/java/com.sap.sailing.server.testsupport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.testsupport diff --git a/java/com.sap.sailing.server.trackfiles.test/.project b/java/com.sap.sailing.server.trackfiles.test/.project index 19d323089ec..9fc63b8d93c 100644 --- a/java/com.sap.sailing.server.trackfiles.test/.project +++ b/java/com.sap.sailing.server.trackfiles.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.trackfiles.test diff --git a/java/com.sap.sailing.server.trackfiles/.project b/java/com.sap.sailing.server.trackfiles/.project index d4063f95610..7a0cf6e8796 100644 --- a/java/com.sap.sailing.server.trackfiles/.project +++ b/java/com.sap.sailing.server.trackfiles/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server.trackfiles diff --git a/java/com.sap.sailing.server/.project b/java/com.sap.sailing.server/.project index a8b7a47686a..5b74fb53be9 100755 --- a/java/com.sap.sailing.server/.project +++ b/java/com.sap.sailing.server/.project @@ -1,4 +1,5 @@ + com.sap.sailing.server diff --git a/java/com.sap.sailing.shared.persistence/.project b/java/com.sap.sailing.shared.persistence/.project index 4dda6169362..66f484685e5 100644 --- a/java/com.sap.sailing.shared.persistence/.project +++ b/java/com.sap.sailing.shared.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sailing.shared.persistence diff --git a/java/com.sap.sailing.shared.server.gateway/.project b/java/com.sap.sailing.shared.server.gateway/.project index 9e5ed6cf27f..5284c07f7d2 100644 --- a/java/com.sap.sailing.shared.server.gateway/.project +++ b/java/com.sap.sailing.shared.server.gateway/.project @@ -1,4 +1,5 @@ + com.sap.sailing.shared.server.gateway diff --git a/java/com.sap.sailing.shared.server/.project b/java/com.sap.sailing.shared.server/.project index 4fafc652ff4..a15e5231f9e 100644 --- a/java/com.sap.sailing.shared.server/.project +++ b/java/com.sap.sailing.shared.server/.project @@ -1,4 +1,5 @@ + com.sap.sailing.shared.server diff --git a/java/com.sap.sailing.simulator.test/.project b/java/com.sap.sailing.simulator.test/.project index 6e91001fe19..c0be9c204ca 100644 --- a/java/com.sap.sailing.simulator.test/.project +++ b/java/com.sap.sailing.simulator.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.simulator.test diff --git a/java/com.sap.sailing.simulator/.project b/java/com.sap.sailing.simulator/.project index f86820f24df..98130d331fb 100644 --- a/java/com.sap.sailing.simulator/.project +++ b/java/com.sap.sailing.simulator/.project @@ -1,4 +1,5 @@ + com.sap.sailing.simulator diff --git a/java/com.sap.sailing.targetplatform.base/.project b/java/com.sap.sailing.targetplatform.base/.project index d67ec4527f9..cd8d3ce4df1 100644 --- a/java/com.sap.sailing.targetplatform.base/.project +++ b/java/com.sap.sailing.targetplatform.base/.project @@ -1,4 +1,5 @@ + com.sap.sailing.targetplatform.base diff --git a/java/com.sap.sailing.targetplatform.updater/.project b/java/com.sap.sailing.targetplatform.updater/.project index 990cc4fafe4..8248947eb65 100755 --- a/java/com.sap.sailing.targetplatform.updater/.project +++ b/java/com.sap.sailing.targetplatform.updater/.project @@ -1,4 +1,5 @@ + com.sap.sailing.targetplatform.updater diff --git a/java/com.sap.sailing.targetplatform/.project b/java/com.sap.sailing.targetplatform/.project index 7122082d0c5..f1836c86286 100644 --- a/java/com.sap.sailing.targetplatform/.project +++ b/java/com.sap.sailing.targetplatform/.project @@ -1,4 +1,5 @@ + com.sap.sailing.targetplatform diff --git a/java/com.sap.sailing.udpconnector/.project b/java/com.sap.sailing.udpconnector/.project index 4fd650e213a..3ee1e64df14 100755 --- a/java/com.sap.sailing.udpconnector/.project +++ b/java/com.sap.sailing.udpconnector/.project @@ -1,4 +1,5 @@ + com.sap.sailing.udpconnector diff --git a/java/com.sap.sailing.velum.resultimport.test/.project b/java/com.sap.sailing.velum.resultimport.test/.project index 0727bc8ef05..7d44f5e2172 100644 --- a/java/com.sap.sailing.velum.resultimport.test/.project +++ b/java/com.sap.sailing.velum.resultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.velum.resultimport.test diff --git a/java/com.sap.sailing.velum.resultimport/.project b/java/com.sap.sailing.velum.resultimport/.project index 11d2ba9be5d..a9d230457f1 100644 --- a/java/com.sap.sailing.velum.resultimport/.project +++ b/java/com.sap.sailing.velum.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.velum.resultimport diff --git a/java/com.sap.sailing.windestimation.lab/.project b/java/com.sap.sailing.windestimation.lab/.project index da7d1788eb3..7c23be06770 100644 --- a/java/com.sap.sailing.windestimation.lab/.project +++ b/java/com.sap.sailing.windestimation.lab/.project @@ -1,4 +1,5 @@ + com.sap.sailing.windestimation.lab diff --git a/java/com.sap.sailing.windestimation.test/.project b/java/com.sap.sailing.windestimation.test/.project index 485faa516b5..e9c9a13f061 100644 --- a/java/com.sap.sailing.windestimation.test/.project +++ b/java/com.sap.sailing.windestimation.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.windestimation.test diff --git a/java/com.sap.sailing.windestimation/.project b/java/com.sap.sailing.windestimation/.project index 67525026b31..a2e84523ca2 100644 --- a/java/com.sap.sailing.windestimation/.project +++ b/java/com.sap.sailing.windestimation/.project @@ -1,4 +1,5 @@ + com.sap.sailing.windestimation diff --git a/java/com.sap.sailing.www/.project b/java/com.sap.sailing.www/.project index 9df335c4c2e..56e5fe9e51a 100644 --- a/java/com.sap.sailing.www/.project +++ b/java/com.sap.sailing.www/.project @@ -1,4 +1,5 @@ + com.sap.sailing.www diff --git a/java/com.sap.sailing.xmlexport.test/.project b/java/com.sap.sailing.xmlexport.test/.project index 934210d75df..e9b699be121 100755 --- a/java/com.sap.sailing.xmlexport.test/.project +++ b/java/com.sap.sailing.xmlexport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.xmlexport.test diff --git a/java/com.sap.sailing.xmlexport/.project b/java/com.sap.sailing.xmlexport/.project index bd3d72fce05..54ecb7bf47e 100755 --- a/java/com.sap.sailing.xmlexport/.project +++ b/java/com.sap.sailing.xmlexport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.xmlexport diff --git a/java/com.sap.sailing.xrr.resultimport.test/.project b/java/com.sap.sailing.xrr.resultimport.test/.project index 45f7b28d352..817b9aa7bdc 100755 --- a/java/com.sap.sailing.xrr.resultimport.test/.project +++ b/java/com.sap.sailing.xrr.resultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.xrr.resultimport.test diff --git a/java/com.sap.sailing.xrr.resultimport/.project b/java/com.sap.sailing.xrr.resultimport/.project index f10ca5c0495..13e9189530a 100755 --- a/java/com.sap.sailing.xrr.resultimport/.project +++ b/java/com.sap.sailing.xrr.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.xrr.resultimport diff --git a/java/com.sap.sailing.xrr.schema/.project b/java/com.sap.sailing.xrr.schema/.project index 764fa04e83c..4183608d61d 100755 --- a/java/com.sap.sailing.xrr.schema/.project +++ b/java/com.sap.sailing.xrr.schema/.project @@ -1,4 +1,5 @@ + com.sap.sailing.xrr.schema diff --git a/java/com.sap.sailing.xrr.structureimport/.project b/java/com.sap.sailing.xrr.structureimport/.project index e45cbe00588..0e27b7c8ba8 100755 --- a/java/com.sap.sailing.xrr.structureimport/.project +++ b/java/com.sap.sailing.xrr.structureimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.xrr.structureimport diff --git a/java/com.sap.sailing.yachtscoring.resultimport.test/.project b/java/com.sap.sailing.yachtscoring.resultimport.test/.project index cabaf35e444..6eae1cf64ef 100644 --- a/java/com.sap.sailing.yachtscoring.resultimport.test/.project +++ b/java/com.sap.sailing.yachtscoring.resultimport.test/.project @@ -1,4 +1,5 @@ + com.sap.sailing.yachtscoring.resultimport.test diff --git a/java/com.sap.sailing.yachtscoring.resultimport/.project b/java/com.sap.sailing.yachtscoring.resultimport/.project index 7de1281b339..44b63db45f7 100644 --- a/java/com.sap.sailing.yachtscoring.resultimport/.project +++ b/java/com.sap.sailing.yachtscoring.resultimport/.project @@ -1,4 +1,5 @@ + com.sap.sailing.yachtscoring.resultimport diff --git a/java/com.sap.sse.common.test/.project b/java/com.sap.sse.common.test/.project index c108b8c5327..385377a8ec7 100644 --- a/java/com.sap.sse.common.test/.project +++ b/java/com.sap.sse.common.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.common.test diff --git a/java/com.sap.sse.common/.project b/java/com.sap.sse.common/.project index c28bb7a5e5a..4d040045c4c 100644 --- a/java/com.sap.sse.common/.project +++ b/java/com.sap.sse.common/.project @@ -1,4 +1,5 @@ + com.sap.sse.common diff --git a/java/com.sap.sse.datamining.annotations/.project b/java/com.sap.sse.datamining.annotations/.project index 58c1e5bd5f5..470a59f5a32 100644 --- a/java/com.sap.sse.datamining.annotations/.project +++ b/java/com.sap.sse.datamining.annotations/.project @@ -1,4 +1,5 @@ + com.sap.sse.datamining.annotations diff --git a/java/com.sap.sse.datamining.shared/.project b/java/com.sap.sse.datamining.shared/.project index 4930800502d..7c8a2e96952 100644 --- a/java/com.sap.sse.datamining.shared/.project +++ b/java/com.sap.sse.datamining.shared/.project @@ -1,4 +1,5 @@ + com.sap.sse.datamining.shared diff --git a/java/com.sap.sse.datamining.test/.project b/java/com.sap.sse.datamining.test/.project index 3867b76f31a..f1515b62838 100644 --- a/java/com.sap.sse.datamining.test/.project +++ b/java/com.sap.sse.datamining.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.datamining.test diff --git a/java/com.sap.sse.datamining.ui.test/.project b/java/com.sap.sse.datamining.ui.test/.project index ad59090e252..a4d5371a91f 100644 --- a/java/com.sap.sse.datamining.ui.test/.project +++ b/java/com.sap.sse.datamining.ui.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.datamining.ui.test diff --git a/java/com.sap.sse.datamining.ui/.project b/java/com.sap.sse.datamining.ui/.project index 003e74ffac2..046f567eb4d 100644 --- a/java/com.sap.sse.datamining.ui/.project +++ b/java/com.sap.sse.datamining.ui/.project @@ -1,4 +1,5 @@ + com.sap.sse.datamining.ui diff --git a/java/com.sap.sse.datamining/.project b/java/com.sap.sse.datamining/.project index 233a003e667..dd2a08ca549 100644 --- a/java/com.sap.sse.datamining/.project +++ b/java/com.sap.sse.datamining/.project @@ -1,4 +1,5 @@ + com.sap.sse.datamining diff --git a/java/com.sap.sse.debranding/.project b/java/com.sap.sse.debranding/.project index d4fa376096f..b2de87386a4 100644 --- a/java/com.sap.sse.debranding/.project +++ b/java/com.sap.sse.debranding/.project @@ -1,4 +1,5 @@ + com.sap.sse.debranding diff --git a/java/com.sap.sse.feature.runtime/.project b/java/com.sap.sse.feature.runtime/.project index ff51f7753be..39640ee3ac3 100644 --- a/java/com.sap.sse.feature.runtime/.project +++ b/java/com.sap.sse.feature.runtime/.project @@ -1,4 +1,5 @@ + com.sap.sse.feature.runtime diff --git a/java/com.sap.sse.feature/.project b/java/com.sap.sse.feature/.project index 41759ea04fa..3f82438b047 100644 --- a/java/com.sap.sse.feature/.project +++ b/java/com.sap.sse.feature/.project @@ -1,4 +1,5 @@ + com.sap.sse.feature diff --git a/java/com.sap.sse.filestorage.test/.project b/java/com.sap.sse.filestorage.test/.project index abfcae8ae1a..a0f2ceb0356 100644 --- a/java/com.sap.sse.filestorage.test/.project +++ b/java/com.sap.sse.filestorage.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.filestorage.test diff --git a/java/com.sap.sse.filestorage/.project b/java/com.sap.sse.filestorage/.project index d769cf7f224..b24d917ec3f 100755 --- a/java/com.sap.sse.filestorage/.project +++ b/java/com.sap.sse.filestorage/.project @@ -1,4 +1,5 @@ + com.sap.sse.filestorage diff --git a/java/com.sap.sse.gwt.adminconsole/.project b/java/com.sap.sse.gwt.adminconsole/.project index 045c56ed0c0..0349faa6abd 100755 --- a/java/com.sap.sse.gwt.adminconsole/.project +++ b/java/com.sap.sse.gwt.adminconsole/.project @@ -1,4 +1,5 @@ + com.sap.sse.gwt.adminconsole diff --git a/java/com.sap.sse.gwt.test/.project b/java/com.sap.sse.gwt.test/.project index 537b0feb168..47b20853f67 100644 --- a/java/com.sap.sse.gwt.test/.project +++ b/java/com.sap.sse.gwt.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.gwt.test diff --git a/java/com.sap.sse.gwt/.project b/java/com.sap.sse.gwt/.project index c975154713e..f9e0fb4aa82 100644 --- a/java/com.sap.sse.gwt/.project +++ b/java/com.sap.sse.gwt/.project @@ -1,4 +1,5 @@ + com.sap.sse.gwt diff --git a/java/com.sap.sse.jersey.jaxbdependencyfragment/.project b/java/com.sap.sse.jersey.jaxbdependencyfragment/.project index 9036413ab0b..bacb0b5e9e0 100755 --- a/java/com.sap.sse.jersey.jaxbdependencyfragment/.project +++ b/java/com.sap.sse.jersey.jaxbdependencyfragment/.project @@ -1,4 +1,5 @@ + com.sap.sse.jersey.jaxbdependencyfragment diff --git a/java/com.sap.sse.landscape.aws.common/.project b/java/com.sap.sse.landscape.aws.common/.project index eb6b5d0abc3..08d83e78da1 100755 --- a/java/com.sap.sse.landscape.aws.common/.project +++ b/java/com.sap.sse.landscape.aws.common/.project @@ -1,4 +1,5 @@ + com.sap.sse.landscape.aws.common diff --git a/java/com.sap.sse.landscape.aws.persistence/.project b/java/com.sap.sse.landscape.aws.persistence/.project index b1b0feca6ee..1c9acf4258a 100755 --- a/java/com.sap.sse.landscape.aws.persistence/.project +++ b/java/com.sap.sse.landscape.aws.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sse.landscape.aws.persistence diff --git a/java/com.sap.sse.landscape.aws.test/.project b/java/com.sap.sse.landscape.aws.test/.project index dc09cd68bd6..37568c76821 100755 --- a/java/com.sap.sse.landscape.aws.test/.project +++ b/java/com.sap.sse.landscape.aws.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.landscape.aws.test diff --git a/java/com.sap.sse.landscape.aws/.project b/java/com.sap.sse.landscape.aws/.project index 1507dbc517a..74baa97229b 100755 --- a/java/com.sap.sse.landscape.aws/.project +++ b/java/com.sap.sse.landscape.aws/.project @@ -1,4 +1,5 @@ + com.sap.sse.landscape.aws diff --git a/java/com.sap.sse.landscape.common/.project b/java/com.sap.sse.landscape.common/.project index 7853e6be409..a9aa6c51241 100755 --- a/java/com.sap.sse.landscape.common/.project +++ b/java/com.sap.sse.landscape.common/.project @@ -1,4 +1,5 @@ + com.sap.sse.landscape.common diff --git a/java/com.sap.sse.landscape/.project b/java/com.sap.sse.landscape/.project index e3fb30b62d7..a35e63a3427 100755 --- a/java/com.sap.sse.landscape/.project +++ b/java/com.sap.sse.landscape/.project @@ -1,4 +1,5 @@ + com.sap.sse.landscape diff --git a/java/com.sap.sse.mail.replication.test/.project b/java/com.sap.sse.mail.replication.test/.project index 51fa2fe50fb..321b25dfd2e 100755 --- a/java/com.sap.sse.mail.replication.test/.project +++ b/java/com.sap.sse.mail.replication.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.mail.replication.test diff --git a/java/com.sap.sse.mail.replication.testsupport/.project b/java/com.sap.sse.mail.replication.testsupport/.project index 1d1e9965612..7e8900a05f1 100755 --- a/java/com.sap.sse.mail.replication.testsupport/.project +++ b/java/com.sap.sse.mail.replication.testsupport/.project @@ -1,4 +1,5 @@ + com.sap.sse.mail.replication.testsupport diff --git a/java/com.sap.sse.mail/.project b/java/com.sap.sse.mail/.project index a9824e60a43..7c93d36ca9d 100755 --- a/java/com.sap.sse.mail/.project +++ b/java/com.sap.sse.mail/.project @@ -1,4 +1,5 @@ + com.sap.sse.mail diff --git a/java/com.sap.sse.mongodb/.project b/java/com.sap.sse.mongodb/.project index c39b3395c38..e121ff6fa51 100755 --- a/java/com.sap.sse.mongodb/.project +++ b/java/com.sap.sse.mongodb/.project @@ -1,4 +1,5 @@ + com.sap.sse.mongodb diff --git a/java/com.sap.sse.operationaltransformation.test/.project b/java/com.sap.sse.operationaltransformation.test/.project index 5545736d716..f7e79174a15 100755 --- a/java/com.sap.sse.operationaltransformation.test/.project +++ b/java/com.sap.sse.operationaltransformation.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.operationaltransformation.test diff --git a/java/com.sap.sse.operationaltransformation/.project b/java/com.sap.sse.operationaltransformation/.project index de9bfe64079..166bfcccebe 100755 --- a/java/com.sap.sse.operationaltransformation/.project +++ b/java/com.sap.sse.operationaltransformation/.project @@ -1,4 +1,5 @@ + com.sap.sse.operationaltransformation diff --git a/java/com.sap.sse.replication.interfaces/.project b/java/com.sap.sse.replication.interfaces/.project index 6379d22889e..1b70a11ee27 100755 --- a/java/com.sap.sse.replication.interfaces/.project +++ b/java/com.sap.sse.replication.interfaces/.project @@ -1,4 +1,5 @@ + com.sap.sse.replication.interfaces diff --git a/java/com.sap.sse.replication.persistence/.project b/java/com.sap.sse.replication.persistence/.project index d545251231e..4e64b54c3fc 100644 --- a/java/com.sap.sse.replication.persistence/.project +++ b/java/com.sap.sse.replication.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sse.replication.persistence diff --git a/java/com.sap.sse.replication.testsupport/.project b/java/com.sap.sse.replication.testsupport/.project index b9ba602570e..473d01d5bc8 100755 --- a/java/com.sap.sse.replication.testsupport/.project +++ b/java/com.sap.sse.replication.testsupport/.project @@ -1,4 +1,5 @@ + com.sap.sse.replication.testsupport diff --git a/java/com.sap.sse.replication/.project b/java/com.sap.sse.replication/.project index 968a30d87fa..e57ea3f60de 100644 --- a/java/com.sap.sse.replication/.project +++ b/java/com.sap.sse.replication/.project @@ -1,4 +1,5 @@ + com.sap.sse.replication diff --git a/java/com.sap.sse.security.common/.project b/java/com.sap.sse.security.common/.project index 470b6a0c0d2..777f2827146 100755 --- a/java/com.sap.sse.security.common/.project +++ b/java/com.sap.sse.security.common/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.common diff --git a/java/com.sap.sse.security.interface/.project b/java/com.sap.sse.security.interface/.project index a1bdef0ba7a..26a557b5ad6 100644 --- a/java/com.sap.sse.security.interface/.project +++ b/java/com.sap.sse.security.interface/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.interface diff --git a/java/com.sap.sse.security.persistence.test/.project b/java/com.sap.sse.security.persistence.test/.project index 204d81c317e..d737a1c1133 100755 --- a/java/com.sap.sse.security.persistence.test/.project +++ b/java/com.sap.sse.security.persistence.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.persistence.test diff --git a/java/com.sap.sse.security.persistence/.project b/java/com.sap.sse.security.persistence/.project index a155be2d55a..ab15e591256 100755 --- a/java/com.sap.sse.security.persistence/.project +++ b/java/com.sap.sse.security.persistence/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.persistence diff --git a/java/com.sap.sse.security.replication.test/.project b/java/com.sap.sse.security.replication.test/.project index f22f7fea878..f63e092309b 100755 --- a/java/com.sap.sse.security.replication.test/.project +++ b/java/com.sap.sse.security.replication.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.replication.test diff --git a/java/com.sap.sse.security.storemerging.test/.project b/java/com.sap.sse.security.storemerging.test/.project index c4ac62e11fd..d26d78dfae5 100755 --- a/java/com.sap.sse.security.storemerging.test/.project +++ b/java/com.sap.sse.security.storemerging.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.storemerging.test diff --git a/java/com.sap.sse.security.storemerging/.project b/java/com.sap.sse.security.storemerging/.project index 11fc356fbae..5bf21242fe6 100755 --- a/java/com.sap.sse.security.storemerging/.project +++ b/java/com.sap.sse.security.storemerging/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.storemerging diff --git a/java/com.sap.sse.security.test/.project b/java/com.sap.sse.security.test/.project index 5d73df4afae..f28da631a7b 100644 --- a/java/com.sap.sse.security.test/.project +++ b/java/com.sap.sse.security.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.test diff --git a/java/com.sap.sse.security.testsupport/.project b/java/com.sap.sse.security.testsupport/.project index 00fe204247e..f0264975888 100755 --- a/java/com.sap.sse.security.testsupport/.project +++ b/java/com.sap.sse.security.testsupport/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.testsupport diff --git a/java/com.sap.sse.security.ui/.project b/java/com.sap.sse.security.ui/.project index b2c40005b31..efd77b4d7f0 100644 --- a/java/com.sap.sse.security.ui/.project +++ b/java/com.sap.sse.security.ui/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.ui diff --git a/java/com.sap.sse.security.userstore.mongodb/.project b/java/com.sap.sse.security.userstore.mongodb/.project index e444758d688..52482f5f592 100644 --- a/java/com.sap.sse.security.userstore.mongodb/.project +++ b/java/com.sap.sse.security.userstore.mongodb/.project @@ -1,4 +1,5 @@ + com.sap.sse.security.userstore.mongodb diff --git a/java/com.sap.sse.security/.project b/java/com.sap.sse.security/.project index e77bf03511e..67e7d190d92 100644 --- a/java/com.sap.sse.security/.project +++ b/java/com.sap.sse.security/.project @@ -1,4 +1,5 @@ + com.sap.sse.security diff --git a/java/com.sap.sse.shared.android.test/.project b/java/com.sap.sse.shared.android.test/.project index a32b5afbd2d..67411021192 100644 --- a/java/com.sap.sse.shared.android.test/.project +++ b/java/com.sap.sse.shared.android.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.shared.android.test diff --git a/java/com.sap.sse.shared.android/.project b/java/com.sap.sse.shared.android/.project index 1ced6293522..da98cd83173 100755 --- a/java/com.sap.sse.shared.android/.project +++ b/java/com.sap.sse.shared.android/.project @@ -1,4 +1,5 @@ + com.sap.sse.shared.android diff --git a/java/com.sap.sse.test/.project b/java/com.sap.sse.test/.project index fefd499bfc5..64454f1bb15 100644 --- a/java/com.sap.sse.test/.project +++ b/java/com.sap.sse.test/.project @@ -1,4 +1,5 @@ + com.sap.sse.test diff --git a/java/com.sap.sse.threadmanager/.project b/java/com.sap.sse.threadmanager/.project index f7b52e2c2e9..229558fe49d 100644 --- a/java/com.sap.sse.threadmanager/.project +++ b/java/com.sap.sse.threadmanager/.project @@ -1,4 +1,5 @@ + com.sap.sse.threadmanager diff --git a/java/com.sap.sse/.project b/java/com.sap.sse/.project index ae82bf33116..06b918aa7e9 100755 --- a/java/com.sap.sse/.project +++ b/java/com.sap.sse/.project @@ -1,4 +1,5 @@ + com.sap.sse diff --git a/java/com.tractrac.clientmodule/.project b/java/com.tractrac.clientmodule/.project index 3d2dfd4b8b0..512cd3c52b6 100755 --- a/java/com.tractrac.clientmodule/.project +++ b/java/com.tractrac.clientmodule/.project @@ -1,4 +1,5 @@ + com.tractrac.clientmodule diff --git a/java/elemental2/.project b/java/elemental2/.project index 30e1f9a0d66..9fe73bf3270 100755 --- a/java/elemental2/.project +++ b/java/elemental2/.project @@ -1,4 +1,5 @@ + elemental2 diff --git a/java/net.sf.marineapi.test/.project b/java/net.sf.marineapi.test/.project index e04929144ac..b13800e13f9 100755 --- a/java/net.sf.marineapi.test/.project +++ b/java/net.sf.marineapi.test/.project @@ -1,4 +1,5 @@ + net.sf.marineapi.test diff --git a/java/net.sf.marineapi/.project b/java/net.sf.marineapi/.project index 4dec2646755..579a463c851 100755 --- a/java/net.sf.marineapi/.project +++ b/java/net.sf.marineapi/.project @@ -1,4 +1,5 @@ + net.sf.marineapi diff --git a/java/org.json.simple/.project b/java/org.json.simple/.project index 14f0b0e639d..45c0070cf75 100755 --- a/java/org.json.simple/.project +++ b/java/org.json.simple/.project @@ -1,4 +1,5 @@ + org.json.simple diff --git a/java/org.moxieapps.gwt.highcharts/.project b/java/org.moxieapps.gwt.highcharts/.project index 0b77374036c..48fb3001b36 100755 --- a/java/org.moxieapps.gwt.highcharts/.project +++ b/java/org.moxieapps.gwt.highcharts/.project @@ -1,4 +1,5 @@ + org.moxieapps.gwt.highcharts diff --git a/java/org.mp4parser.isoparser/.project b/java/org.mp4parser.isoparser/.project index 99468a6d49f..e2711782525 100644 --- a/java/org.mp4parser.isoparser/.project +++ b/java/org.mp4parser.isoparser/.project @@ -1,4 +1,5 @@ + org.mp4parser.isoparser diff --git a/java/org.openqa.selenium.osgi/.project b/java/org.openqa.selenium.osgi/.project index 672e20251ec..7a7e2d31ab7 100644 --- a/java/org.openqa.selenium.osgi/.project +++ b/java/org.openqa.selenium.osgi/.project @@ -1,4 +1,5 @@ + org.openqa.selenium.osgi diff --git a/mobile/.project b/mobile/.project index bed704482d7..618e0f712bb 100644 --- a/mobile/.project +++ b/mobile/.project @@ -1,4 +1,5 @@ + mobile diff --git a/wiki/.project b/wiki/.project index 788a2015e18..f8e3f1d7eb3 100644 --- a/wiki/.project +++ b/wiki/.project @@ -1,4 +1,5 @@ + wiki From ba744baca15ffb342f3fec7ed373affe3054953c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 20 Jun 2021 12:17:09 +0200 Subject: [PATCH 32/37] bug5452: limit the time range for wind fix queries to end of tracking if no end time point is provided --- .../sap/sailing/gwt/ui/server/SailingServiceImpl.java | 9 +++++++-- .../shared/racemap/WindStreamletsRaceboardOverlay.java | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index c64df07607f..5ccaa27328e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1519,8 +1519,13 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet if (trackedRace != null) { TimePoint fromTimePoint = from == null ? trackedRace.getStartOfTracking() == null ? trackedRace .getStartOfRace() : trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from); - TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() == null ? - MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : trackedRace.getEndOfRace() : new MillisecondsTimePoint(to); + TimePoint toTimePoint = to == null ? + trackedRace.getEndOfRace() == null ? + trackedRace.getEndOfTracking() == null ? + MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : + trackedRace.getEndOfTracking() : + trackedRace.getEndOfRace() : + new MillisecondsTimePoint(to); if (fromTimePoint != null && toTimePoint != null) { int numberOfFixes = Math.min(SailingServiceConstants.MAX_NUMBER_OF_WIND_FIXES_TO_DELIVER_IN_ONE_CALL, (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis())/resolutionInMilliseconds)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java index 3f2122cad43..93a99930721 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java @@ -279,6 +279,7 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen } } } + // TODO asking with endOfTime==null may produce a whole lot of fixes! GetWindInfoAction getWind = new GetWindInfoAction(sailingService, raceIdentifier, timeOfLastFixOfSource, /* endOfTime */ null, RESOLUTION_IN_MILLIS, windSourceTypeNames, /* onlyUpToNewestEvent */ true); asyncActionsExecutor.execute(getWind, LOAD_WIND_STREAMLET_DATA_CATEGORY, From 7bf2848ade7618bc8f13fbe44c0fe71718bd5133 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 20 Jun 2021 13:02:36 +0200 Subject: [PATCH 33/37] added null check for result of service call in WindStreamletsRaceboardOverlay.updateWindField --- .../gwt/ui/server/SailingServiceImpl.java | 14 +++++-- .../WindStreamletsRaceboardOverlay.java | 41 ++++++++++--------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 5ccaa27328e..c500115c434 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1500,9 +1500,12 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet } /** + * @param from + * if {@code null}, start of tracking is used, and if that's not available, start of race is used. If + * that is also {@code null}, {@code null} is returned by the method. * @param to - * if null, data is returned up to end of race; if the end of race is not known and - * null is used for this parameter, null is returned. + * if null, data is returned up to end of race or, if that is not available, end of + * tracking; it that is not available either, data is returned up to "now-livedelay" * @param onlyUpToNewestEvent * if true, no wind data will be returned for time points later than * {@link TrackedRace#getTimePointOfNewestEvent() trackedRace.getTimePointOfNewestEvent()}. This is @@ -1517,8 +1520,11 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); WindInfoForRaceDTO result = null; if (trackedRace != null) { - TimePoint fromTimePoint = from == null ? trackedRace.getStartOfTracking() == null ? trackedRace - .getStartOfRace() : trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from); + TimePoint fromTimePoint = from == null ? + trackedRace.getStartOfTracking() == null ? + trackedRace.getStartOfRace() : + trackedRace.getStartOfTracking() : + new MillisecondsTimePoint(from); TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() == null ? trackedRace.getEndOfTracking() == null ? diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java index 93a99930721..f40cb07f7da 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java @@ -292,26 +292,29 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen @Override public void onSuccess(WindInfoForRaceDTO result) { - updateAverageLatitudeDeg(result); - // merge the new wind fixes into the existing WindInfoForRaceDTO structure, updating min/max - // confidences - for (Entry e : result.windTrackInfoByWindSource.entrySet()) { - WindTrackInfoDTO windTrackForSource = windInfoForRace.windTrackInfoByWindSource - .get(e.getKey()); - if (windTrackForSource != null) { - final WindTrackInfoDTO resultWindTrackInfoDTO = result.windTrackInfoByWindSource + if (result != null) { + updateAverageLatitudeDeg(result); + // merge the new wind fixes into the existing WindInfoForRaceDTO structure, updating min/max + // confidences + for (Entry e : result.windTrackInfoByWindSource.entrySet()) { + WindTrackInfoDTO windTrackForSource = windInfoForRace.windTrackInfoByWindSource .get(e.getKey()); - windTrackForSource.resolutionOutsideOfWhichNoFixWillBeReturned = resultWindTrackInfoDTO.resolutionOutsideOfWhichNoFixWillBeReturned; - if (windTrackForSource.windFixes == null) { - windTrackForSource.windFixes = resultWindTrackInfoDTO.windFixes; - } else { - windTrackForSource.windFixes.addAll(resultWindTrackInfoDTO.windFixes); - } - if (resultWindTrackInfoDTO.maxWindConfidence > windTrackForSource.maxWindConfidence) { - windTrackForSource.maxWindConfidence = resultWindTrackInfoDTO.maxWindConfidence; - } - if (resultWindTrackInfoDTO.minWindConfidence < windTrackForSource.minWindConfidence) { - windTrackForSource.minWindConfidence = resultWindTrackInfoDTO.minWindConfidence; + if (windTrackForSource != null) { + final WindTrackInfoDTO resultWindTrackInfoDTO = result.windTrackInfoByWindSource + .get(e.getKey()); + windTrackForSource.resolutionOutsideOfWhichNoFixWillBeReturned = resultWindTrackInfoDTO.resolutionOutsideOfWhichNoFixWillBeReturned; + if (windTrackForSource.windFixes == null) { + // TODO bug5584: this takes over the List implementation provided in the GWT RPC response; yet, WindInfoForRaceVectorField depends on this being efficiently binarySearch'able and hence it must be at least a RandomAccess + windTrackForSource.windFixes = resultWindTrackInfoDTO.windFixes; + } else { + windTrackForSource.windFixes.addAll(resultWindTrackInfoDTO.windFixes); + } + if (resultWindTrackInfoDTO.maxWindConfidence > windTrackForSource.maxWindConfidence) { + windTrackForSource.maxWindConfidence = resultWindTrackInfoDTO.maxWindConfidence; + } + if (resultWindTrackInfoDTO.minWindConfidence < windTrackForSource.minWindConfidence) { + windTrackForSource.minWindConfidence = resultWindTrackInfoDTO.minWindConfidence; + } } } } From 30df7c1a52ffec4baffce16058a5807ba303e9dc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jun 2021 10:10:20 +0200 Subject: [PATCH 34/37] bug5584: added TODO --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 1 - .../java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index c500115c434..a678a49b6b1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -2326,7 +2326,6 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet result.windSourcesToExclude = windSourcesToExclude; Map windTrackInfoDTOs = new HashMap(); result.windTrackInfoByWindSource = windTrackInfoDTOs; - for (WindSource windSource: trackedRace.getWindSources()) { windTrackInfoDTOs.put(windSource, new WindTrackInfoDTO()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java index c95da5e7531..63b9ee1f236 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java @@ -170,7 +170,7 @@ public class Swarm implements TimeListener { // animationIntervalMillis if possible double timeDelta = time1 - time0; // log("fps: "+(1000.0/timeDelta)); - loopTimer.schedule((int) Math.max(10, animationIntervalMillis - timeDelta)); + loopTimer.schedule((int) Math.max(10, animationIntervalMillis - timeDelta)); // TODO consider using AnimationScheduler instead! } private void removeBoundsChangeHandler() { From 19969e8f2430223389b6edf85a71f4ecf3f964aa Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jun 2021 18:38:01 +0200 Subject: [PATCH 35/37] bug5584: fetch only one averaged fix per source for streamlets; don't keep fetching when timer is paused; re-use bearing cluster for all particles --- .../sap/sailing/domain/common/WindSource.java | 15 ++ .../gwt/ui/actions/GetRaceMapDataAction.java | 4 +- .../gwt/ui/client/shared/racemap/RaceMap.java | 3 + .../gwt/ui/server/SailingServiceImpl.java | 2 +- .../WindStreamletsRaceboardOverlay.java | 92 ++++------- .../streamlets/PositionDTOWeigher.java | 3 +- .../gwt/ui/simulator/streamlets/Swarm.java | 3 +- .../ui/simulator/streamlets/VectorField.java | 8 +- .../WindInfoForRaceVectorField.java | 145 ++++++++---------- 9 files changed, 118 insertions(+), 157 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSource.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSource.java index c7df1ff7513..de539900e0b 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSource.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSource.java @@ -2,12 +2,27 @@ package com.sap.sailing.domain.common; import java.io.Serializable; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; + /** * Value-object identifying possible sources for wind data. Used to key and select between different {@link WindTrack}s. * Objects of this class have their {@link Object#equals(Object)} and {@link Object#hashCode()} methods defined * accordingly. + *

      + * + * While a wind source may represent, e.g., a wind sensor which may move around, such as on a coach boat, separate wind + * sources should be used to identify separate measurement spots. For example, the wind data from a GRIB file should + * have a separate wind source per position in the GRIB file's grid. Imagine a wind source as something that could be + * displayed as a small wind arrow on a map. Consider using {@link WindSourceWithAdditionalID} to keep wind sources of + * the same type apart by giving them distinct IDs. + *

      + * + * Conceptually, this would also work for {@link WindSourceType#MANEUVER_BASED_ESTIMATION}, but then every competitor + * would have to be its own wind source. While theoretically possible, this may not pay off given the ultra-low + * confidence of that specific source, and hence it seems okay that the respective virtual wind track combines all fixes + * inferred from maneuvers into the same single wind source of type {@link WindSourceType#MANEUVER_BASED_ESTIMATION}. * * @author Axel Uhl (d043530) * diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java index 3d1ba78195b..0911234797a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java @@ -35,7 +35,7 @@ public class GetRaceMapDataAction extends AbstractGetMapRelatedDataAction() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index 47d1f32e02f..acfc02cc1c2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -1147,6 +1147,9 @@ public class RaceMap extends AbstractCompositeComponent impleme raceIdentifier, newTime, transitionTimeInMillis, competitorsToShow, isRedraw, selectedDetailType, selectedDetailTypeChanged); // draw the wind into the map, get the combined wind + // TODO bug5584 shouldn't we extend this to fetch the one decisive averaged fix from *all* relevant sources? + // TODO bug5584 Then we could push the wind data received here into the WindInfoForRaceVectorField instance providing the data and + // TODO bug5584 interpolation for the streamlet overlay if switched on. List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); windSourceTypeNames.add(WindSourceType.WINDFINDER.name()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index a678a49b6b1..7eaa5932541 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1387,7 +1387,7 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); getSecurityService().checkCurrentUserReadPermission(trackedRace); WindInfoForRaceDTO result = getAveragedWindInfo(new MillisecondsTimePoint(from), millisecondsStepWidth, numberOfFixes, - windSourceTypeNames, trackedRace, /* onlyUpToNewestEvent FIXME why not pass through onlyUpToNewestEvent here??? */ true, includeCombinedWindForAllLegMiddles); + windSourceTypeNames, trackedRace, onlyUpToNewestEvent, includeCombinedWindForAllLegMiddles); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java index f40cb07f7da..ae276a0c64d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Collectors; import com.google.gwt.canvas.client.Canvas; import com.google.gwt.canvas.dom.client.Context2d; @@ -23,7 +24,6 @@ import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.racemap.CoordinateSystem; -import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; import com.sap.sailing.gwt.ui.simulator.StreamletParameters; @@ -39,6 +39,7 @@ import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.mutationobserver.ElementStyleMutationObserver; import com.sap.sse.gwt.client.mutationobserver.ElementStyleMutationObserver.DomStyleMutationCallback; +import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.Timer; /** @@ -49,7 +50,7 @@ import com.sap.sse.gwt.client.player.Timer; * @author Axel Uhl (D043530) * */ -public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implements ColorMapperChangedListener { +public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implements ColorMapperChangedListener, TimeListener { public static final String LOAD_WIND_STREAMLET_DATA_CATEGORY = "loadWindStreamletData"; private static final int animationIntervalMillis = 40; private static final long RESOLUTION_IN_MILLIS = 5000; @@ -69,12 +70,11 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen private Canvas streamletLegend; private boolean firstColoring = true; private boolean colored; - private long latitudeCount; - private double latitudeSum; private final NumberFormat numberFormatOneDecimal = NumberFormatterFactory.getDecimalFormat(1); private ElementStyleMutationObserver observer; private boolean dragging = false; private boolean isAttached = false, startObserverWhenAttached = false; + private boolean timeChangedSinceLastUpdate = true; public WindStreamletsRaceboardOverlay(MapWidget map, int zIndex, final Timer timer, RegattaAndRaceIdentifier raceIdentifier, SailingServiceAsync sailingService, @@ -90,33 +90,14 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen windInfoForRace.raceIsKnownToStartUpwind = true; // default windInfoForRace.windSourcesToExclude = new HashSet<>(); windInfoForRace.windTrackInfoByWindSource = new HashMap<>(); - updateAverageLatitudeDeg(windInfoForRace); this.windField = new WindInfoForRaceVectorField(windInfoForRace, /* frames per second */ 1000.0 / animationIntervalMillis, coordinateSystem); this.timer = timer; + this.timer.addTimeListener(this); getCanvas().getElement().setId("swarm-display"); createStreamletLegend(map); } - public double getAverageLatitudeDeg() { - return latitudeCount > 0 ? latitudeSum / latitudeCount : 0; - } - - private void updateAverageLatitudeDeg(WindInfoForRaceDTO windInfoForRace) { - for (Entry windSourceAndTrack : windInfoForRace.windTrackInfoByWindSource - .entrySet()) { - for (WindDTO wind : windSourceAndTrack.getValue().windFixes) { - if (wind.position != null) { - latitudeSum += wind.position.getLatDeg(); - latitudeCount++; - } - } - } - if (latitudeCount > 0) { - windField.setAverageLatitudeDeg(latitudeSum / latitudeCount); - } - } - private void createStreamletLegend(MapWidget map) { streamletLegend = Canvas.createIfSupported(); streamletLegend.addStyleName("MapStreamletLegend"); @@ -200,11 +181,19 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen this.swarm.getColorMapper().addListener(this); } + @Override + public void timeChanged(Date newTime, Date oldTime) { + timeChangedSinceLastUpdate = true; + } + private void scheduleWindDataRefresh() { scheduler.scheduleFixedPeriod(new RepeatingCommand() { @Override public boolean execute() { - updateWindField(); + if (timeChangedSinceLastUpdate) { + updateWindField(); + timeChangedSinceLastUpdate = false; + } return visible; } }, WIND_FETCH_INTERVAL_IN_MILLIS); @@ -215,7 +204,7 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen return visible; } }, CHECK_WIND_SOURCE_INTERVAL_IN_MILLIS); - // Now run things once, first updating the wind sources, then grabbing the wind from those sources: + // Now run things once, first updating the wind sources so that when streamlets are enabled, wind can be queried quickly: updateWindSourcesToObserve(new Runnable() { @Override public void run() { @@ -265,23 +254,17 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen })); } + /** + * Fetches data for the {@link Timer#getTime() current time} set in the {@link #timer}. For each wind source to observe, + * a single averaged fix for the current time is requested which is then used to replace the vector field's contents. + * This way, the vector field has constant memory footprint, wind queries are fast and independent of race length, + * and wind track time resolution. + */ private void updateWindField() { - Date timeOfLastFixOfSource = null; - Set windSourceTypeNames = new HashSet<>(); - for (final Entry e : windInfoForRace.windTrackInfoByWindSource.entrySet()) { - if (!Util.contains(windInfoForRace.windSourcesToExclude, e.getKey())) { - windSourceTypeNames.add(e.getKey().getType().name()); - if (e.getValue().windFixes != null && !e.getValue().windFixes.isEmpty()) { - // TODO this should better be a per wind source time range; furthermore, only real fixes should be - // requested / transmitted - timeOfLastFixOfSource = new Date( - e.getValue().windFixes.get(e.getValue().windFixes.size() - 1).measureTimepoint + 1); - } - } - } - // TODO asking with endOfTime==null may produce a whole lot of fixes! - GetWindInfoAction getWind = new GetWindInfoAction(sailingService, raceIdentifier, timeOfLastFixOfSource, - /* endOfTime */ null, RESOLUTION_IN_MILLIS, windSourceTypeNames, /* onlyUpToNewestEvent */ true); + final Set windSourceTypeNames = windInfoForRace.windTrackInfoByWindSource.keySet().stream().map( + windSource->windSource.getType().name()).collect(Collectors.toSet()); + GetWindInfoAction getWind = new GetWindInfoAction(sailingService, raceIdentifier, timer.getTime(), RESOLUTION_IN_MILLIS, + /* number of fixes */ 1, windSourceTypeNames, /* onlyUpToNewestEvent */ true); asyncActionsExecutor.execute(getWind, LOAD_WIND_STREAMLET_DATA_CATEGORY, new MarkedAsyncCallback<>(new AsyncCallback() { @Override @@ -293,30 +276,7 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen @Override public void onSuccess(WindInfoForRaceDTO result) { if (result != null) { - updateAverageLatitudeDeg(result); - // merge the new wind fixes into the existing WindInfoForRaceDTO structure, updating min/max - // confidences - for (Entry e : result.windTrackInfoByWindSource.entrySet()) { - WindTrackInfoDTO windTrackForSource = windInfoForRace.windTrackInfoByWindSource - .get(e.getKey()); - if (windTrackForSource != null) { - final WindTrackInfoDTO resultWindTrackInfoDTO = result.windTrackInfoByWindSource - .get(e.getKey()); - windTrackForSource.resolutionOutsideOfWhichNoFixWillBeReturned = resultWindTrackInfoDTO.resolutionOutsideOfWhichNoFixWillBeReturned; - if (windTrackForSource.windFixes == null) { - // TODO bug5584: this takes over the List implementation provided in the GWT RPC response; yet, WindInfoForRaceVectorField depends on this being efficiently binarySearch'able and hence it must be at least a RandomAccess - windTrackForSource.windFixes = resultWindTrackInfoDTO.windFixes; - } else { - windTrackForSource.windFixes.addAll(resultWindTrackInfoDTO.windFixes); - } - if (resultWindTrackInfoDTO.maxWindConfidence > windTrackForSource.maxWindConfidence) { - windTrackForSource.maxWindConfidence = resultWindTrackInfoDTO.maxWindConfidence; - } - if (resultWindTrackInfoDTO.minWindConfidence < windTrackForSource.minWindConfidence) { - windTrackForSource.minWindConfidence = resultWindTrackInfoDTO.minWindConfidence; - } - } - } + windField.updateWindInfo(result); } } })); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/PositionDTOWeigher.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/PositionDTOWeigher.java index 1bbca5f5673..0ba7fd94b0b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/PositionDTOWeigher.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/PositionDTOWeigher.java @@ -8,7 +8,7 @@ import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sse.common.Distance; /** - * A weigher that uses a {@link DegreePosition} and a {@link Date} to compute a confidence based on space and time + * A weigher that uses a {@link DegreePosition} and a {@link Date} to compute a confidence based on spatial * distance. If the fix or the request parameter in a call to * {@link #getConfidence(com.sap.sse.common.Util.Pair, com.sap.sse.common.Util.Pair)} have a null * {@link Position} then no distance-based confidence is considered, and only the time difference is taken into account. @@ -29,7 +29,6 @@ public class PositionDTOWeigher implements Weigher { private final AverageLatitudeProvider averageLatitudeDegProvider; public static interface AverageLatitudeProvider { - double getAverageLatitudeDeg(); double getCosineOfAverageLatitude(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java index 63b9ee1f236..d575dabea4f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java @@ -346,8 +346,7 @@ public class Swarm implements TimeListener { particle.v = null; } } else { - // particle timed out (age became 0) or was never created (e.g., weight too low); try to create a new - // one + // particle timed out (age became 0) or was never created (e.g., weight too low); try to create a new one particles[idx] = this.recycleOrCreateParticle(particles[idx]); } if (particles[idx] != null && particles[idx].v != null) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/VectorField.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/VectorField.java index 3d3cf6fccee..8aa5027730e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/VectorField.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/VectorField.java @@ -35,7 +35,13 @@ public interface VectorField { * position in the map's coordinate system; needs to be mapped backwards through a * {@link CoordinateSystem} to produce a real-world {@link Position} * @param at - * the time at which to query the vector field + * the time at which to query the vector field. This vector field and its client(s) may agree on how to + * handle the time ranges. For example, a vector field implementation may buffer data for a larger time + * range, and clients can then query this vector field for different time points successfully. Or + * this vector field and its client(s) agree what a valid time range or even single time point may be, + * and this vector field then buffers only the data for a very short time range or even a single time + * point and then basically ignores this argument, knowing that its client(s) already make an assumption + * about the time for which this vector field will hold and deliver data. * * @return the speed/direction vector that tells how a particle will fly at this position in the vector field, or * null if there should not be a flying particle, e.g., because the field does not know how a diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/WindInfoForRaceVectorField.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/WindInfoForRaceVectorField.java index 16cd033c1ce..2cf0f026869 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/WindInfoForRaceVectorField.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/WindInfoForRaceVectorField.java @@ -1,7 +1,5 @@ package com.sap.sailing.gwt.ui.simulator.streamlets; -import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map.Entry; @@ -25,9 +23,10 @@ import com.sap.sse.common.impl.DegreeBearingImpl; /** * Implements the {@link VectorField} interface by providing real wind data from a TrackedRace which has - * been received in the form of one or more {@link WindInfoForRaceDTO} objects. The field uses time and space distances - * to weigh the wind measurements provided, leading to a spatially-resolved wind field that can be visualized. Its - * bounds are infinite as time/space-weighed averages can generally be computed anywhere. + * been received in the form of one or more {@link WindInfoForRaceDTO} objects. The field uses spatial distances to + * weigh the wind measurements provided, assuming there is always only one fix contained in each wind track, leading to + * a spatially-resolved wind field that can be visualized. Its bounds are infinite as time/space-weighed averages can + * generally be computed anywhere. *

      * * The vectors produced by this field are sized such that their x/y values represent 1/60th of a longitude/latitude @@ -44,21 +43,21 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP private static final double MAX_WIND_SPEED_IN_KNOTS = 40; private final LatLngBounds infiniteBounds = LatLngBounds.newInstance(LatLng.newInstance(-90, -180), LatLng.newInstance(90, 180)); private final Weigher weigher; - private double averageLatitudeDeg; private double averageLatitudeCosine; private double knotsInDegreePerFrame; + private long latitudeCount; + private double latitudeSum; private final CoordinateSystem coordinateSystem; - private final BearingWithConfidenceCluster bearingCluster; - - private final Comparator windByRequestTimePointComparator = new Comparator() { - @Override - public int compare(WindDTO o1, WindDTO o2) { - return o1.requestTimepoint > o2.requestTimepoint ? 1 : o1.requestTimepoint == o2.requestTimepoint ? 0 : -1; - } - }; private final WindInfoForRaceDTO windInfoForRace; + /** + * @param windInfoForRace + * the underlying wind data; updated outside of this class, but this instance expects to be + * {@link #updateWindInfo(WindInfoForRaceDTO) notified} about such updates because it will take the + * opportunity to update the {@link BearingWithConfidenceCluster cluster of wind directions} only once + * per update so that it doesn't need to be re-constructed for each particle that is to be animated. + */ public WindInfoForRaceVectorField(WindInfoForRaceDTO windInfoForRace, double framesPerSecond, CoordinateSystem coordinateSystem) { this.coordinateSystem = coordinateSystem; this.windInfoForRace = windInfoForRace; @@ -67,20 +66,53 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP bearingCluster = new BearingWithConfidenceCluster<>(weigher); } + /** + * Replaces the internal structures of the final {@link #windInfoForRace} based on the contents of {@code newWindInfo} and + * updates the {@link #bearingCluster} to reflect the new wind fixes with their wind directions and confidences. + */ + public void updateWindInfo(WindInfoForRaceDTO newWindInfo) { + // merge the new wind fixes into the existing WindInfoForRaceDTO structure, updating min/max + // confidences + windInfoForRace.windTrackInfoByWindSource = newWindInfo.windTrackInfoByWindSource; + bearingCluster.clear(); + for (final Entry windSourceAndWindTrack : windInfoForRace.windTrackInfoByWindSource.entrySet()) { + if (!Util.contains(windInfoForRace.windSourcesToExclude, windSourceAndWindTrack.getKey())) { + final List windFixes = windSourceAndWindTrack.getValue().windFixes; + if (windFixes != null && !windFixes.isEmpty()) { + WindDTO timewiseClosestFixForWindSource = windFixes.get(0); + if (timewiseClosestFixForWindSource != null) { + final double confidence = timewiseClosestFixForWindSource.confidence == null ? 1 : timewiseClosestFixForWindSource.confidence; + bearingCluster.add(new BearingWithConfidenceImpl( + new DegreeBearingImpl(timewiseClosestFixForWindSource.dampenedTrueWindBearingDeg), confidence, timewiseClosestFixForWindSource.position)); + } + } + } + } + updateAverageLatitudeDeg(newWindInfo); + } + + private void updateAverageLatitudeDeg(WindInfoForRaceDTO windInfoForRace) { + for (Entry windSourceAndTrack : windInfoForRace.windTrackInfoByWindSource.entrySet()) { + for (WindDTO wind : windSourceAndTrack.getValue().windFixes) { + if (wind.position != null) { + latitudeSum += wind.position.getLatDeg(); + latitudeCount++; + } + } + } + if (latitudeCount > 0) { + setAverageLatitudeDeg(latitudeSum / latitudeCount); + } + } + /** * Sets the average latitude used for the simplified approximating distance calculation. Until called, 0.0 is * assumed. */ public void setAverageLatitudeDeg(double averageLatitudeDeg) { - this.averageLatitudeDeg = averageLatitudeDeg; this.averageLatitudeCosine = Math.cos(averageLatitudeDeg/180.0*Math.PI); } - @Override - public double getAverageLatitudeDeg() { - return averageLatitudeDeg; - } - @Override public double getCosineOfAverageLatitude() { return averageLatitudeCosine; @@ -103,19 +135,19 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP final Position p = coordinateSystem.getPosition(mappedPosition); double speedConfidenceSum = 0; double knotSpeedSumScaledByConfidence = 0; - bearingCluster.clear(); for (final Entry windSourceAndWindTrack : windInfoForRace.windTrackInfoByWindSource.entrySet()) { if (!Util.contains(windInfoForRace.windSourcesToExclude, windSourceAndWindTrack.getKey())) { - WindDTO timewiseClosestFixForWindSource = getTimewiseClosestFix(windSourceAndWindTrack.getValue(), at); - if (timewiseClosestFixForWindSource != null) { - final double confidence = (timewiseClosestFixForWindSource.confidence == null ? 1 : timewiseClosestFixForWindSource.confidence) * - weigher.getConfidence(timewiseClosestFixForWindSource.position, p); - if (windSourceAndWindTrack.getKey().getType().useSpeed()) { - speedConfidenceSum += confidence; - knotSpeedSumScaledByConfidence += confidence * timewiseClosestFixForWindSource.dampenedTrueWindSpeedInKnots; + final List windFixes = windSourceAndWindTrack.getValue().windFixes; + if (windFixes != null && !windFixes.isEmpty()) { + WindDTO timewiseClosestFixForWindSource = windFixes.get(0); + if (timewiseClosestFixForWindSource != null) { + final double confidence = (timewiseClosestFixForWindSource.confidence == null ? 1 : timewiseClosestFixForWindSource.confidence) * + weigher.getConfidence(timewiseClosestFixForWindSource.position, p); + if (windSourceAndWindTrack.getKey().getType().useSpeed()) { + speedConfidenceSum += confidence; + knotSpeedSumScaledByConfidence += confidence * timewiseClosestFixForWindSource.dampenedTrueWindSpeedInKnots; + } } - bearingCluster.add(new BearingWithConfidenceImpl( - new DegreeBearingImpl(timewiseClosestFixForWindSource.dampenedTrueWindBearingDeg), confidence, timewiseClosestFixForWindSource.position)); } } } @@ -131,59 +163,6 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP return result; } - /** - * Regular server-side wind tracks will deliver fixes for queries with a time that is far from any - * of the fixes' time point. A respectively reduced confidence will be the result. However, some wind - * track types such as that for the wind estimation may not deliver a fix, not even with low confidence, - * if at the requested time point (give or take some rounding to a time tick resolution) no fix can be - * provided. In this case, the server will not use any value from such a track when computing an average - * including fixes from several tracks.

      - * - * To ensure consistent behavior on the client, this method will check if the wind track is marked - * accordingly and will return null if the track cannot provide a fix within its resolution - * around the requested time point. - */ - private WindDTO getTimewiseClosestFix(WindTrackInfoDTO windTrackInfo, Date at) { - List windFixes = windTrackInfo.windFixes; - final WindDTO preResult; - if (windFixes == null || windFixes.isEmpty()) { - preResult = null; - } else { - final WindDTO atDummy = new WindDTO(); - atDummy.requestTimepoint = at.getTime(); - int pos = Collections.binarySearch(windFixes, atDummy, windByRequestTimePointComparator); - if (pos < 0) { - pos = (-pos) - 1; // now pos points at the insertion point - if (pos == 0 - || (pos < windFixes.size() && - Math.abs(windFixes.get(pos).requestTimepoint - at.getTime()) < Math.abs(windFixes - .get(pos - 1).requestTimepoint - at.getTime()))) { - preResult = windFixes.get(pos); - } else { - // pos doesn't point to the first element, nor is the element at pos time-wise closer than the element at pos-1 - // or pos points beyond the end of the list - preResult = windFixes.get(pos-1); - } - } else { - preResult = windFixes.get(pos); - } - } - final WindDTO result; - // don't return wind fixes if according to the wind track there is a resolution to which times are rounded - // and where null is returned when there is no fix at the rounded time point; mimic this behavior here. See - // also bug 2689 comment #13. - if (windTrackInfo.resolutionOutsideOfWhichNoFixWillBeReturned != null) { - if (preResult == null || Math.abs(preResult.requestTimepoint-at.getTime()) > windTrackInfo.resolutionOutsideOfWhichNoFixWillBeReturned.asMillis()) { - result = null; - } else { - result = preResult; - } - } else { - result = preResult; - } - return result; - } - @Override public double getMotionScale(int zoomLevel) { // This implementation is copied from SimulatorField, hoping it does something useful in combination with From 2cc5b7e52fe1142980a4f49d85b2c91b2d4f4b61 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 21 Jun 2021 18:42:32 +0200 Subject: [PATCH 36/37] adjusted TODO comment from bug5584 to the new bug5586 --- .../sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index acfc02cc1c2..0e3b7406abe 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -1147,9 +1147,9 @@ public class RaceMap extends AbstractCompositeComponent impleme raceIdentifier, newTime, transitionTimeInMillis, competitorsToShow, isRedraw, selectedDetailType, selectedDetailTypeChanged); // draw the wind into the map, get the combined wind - // TODO bug5584 shouldn't we extend this to fetch the one decisive averaged fix from *all* relevant sources? - // TODO bug5584 Then we could push the wind data received here into the WindInfoForRaceVectorField instance providing the data and - // TODO bug5584 interpolation for the streamlet overlay if switched on. + // TODO bug5586 shouldn't we extend this to fetch the one decisive averaged fix from *all* relevant sources? + // TODO bug5586 Then we could push the wind data received here into the WindInfoForRaceVectorField instance providing the data and + // TODO bug5586 interpolation for the streamlet overlay if switched on. List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); windSourceTypeNames.add(WindSourceType.WINDFINDER.name()); From 8b80ef1c0a3d10e0b3e3a02d8ab193f85c3d2a7d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 22 Jun 2021 09:57:10 +0200 Subject: [PATCH 37/37] bug5584 / bug5452: updated release notes --- .../places/whatsnew/resources/SailingAnalyticsNotes.html | 3 +++ java/com.sap.sailing.www/release_notes_admin.html | 2 ++ 2 files changed, 5 insertions(+) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html index a726e83cd13..e3b0465a259 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html @@ -13,6 +13,9 @@ "jumps" in estimated wind directions. Base confidence of wind estimations, compared to sensor-based measurements, has been slightly reduced, as has the base confidence for estimations from downwind legs. +

    • Wind streamlet overlay on race map now scales better for long-distance races. Its spatial + interpolation is now a much closer approximation of the server-side wind field used which + also leads to smoother transitions between sensors distributed across the race course area.
    May 2021
      diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index 5c5a772a7a0..d228fabc54a 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -29,6 +29,8 @@
    • Bugfix in landscape management: when upgrading a replica set whose SERVER_NAME property does not equal the hostname used to address it, the temporary replica was trying to replicate from {SERVER_NAME}.sapsailing.com instead of {hostname}.sapsailing.com.
    • +
    • GRIB file import in the "Tracked Races / Wind" panel now turns each position into a separate wind source, + leading to better visual interpolation in streamlet display on map.

    May 2021