java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseChangeBasedTrackApproximation.java
... ...
@@ -247,24 +247,7 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
247 247
final GPSFixMoving result;
248 248
final SpeedWithBearing nextSpeed = next.isEstimatedSpeedCached() ? next.getCachedEstimatedSpeed() : track.getEstimatedSpeed(next.getTimePoint());
249 249
if (logFixes) {
250
- final boolean validityCached = next.isValidityCached();
251
- final boolean validity;
252
- if (validityCached) {
253
- validity = next.isValidCached();
254
- } else {
255
- track.lockForRead();
256
- try {
257
- validity = track.isValid(next);
258
- } finally {
259
- track.unlockAfterRead();
260
- }
261
- }
262
- // CSV logging: approxId, fixIndex, fixTimeMillis, validityCached, speedCached, COG, SOG
263
- System.out.println(System.identityHashCode(this) + "," + next.getTimePoint().asMillis() + ","
264
- + next.isValidityCached() + "," + next.isEstimatedSpeedCached() + ","
265
- + (nextSpeed == null ? "null" : nextSpeed.getBearing().getDegrees()) + ","
266
- + (nextSpeed == null ? "null" : nextSpeed.getKnots()) + ","
267
- + validityCached + "," + validity);
250
+ logFix(next, nextSpeed);
268 251
}
269 252
if (nextSpeed != null) {
270 253
numberOfFixesAdded++;
... ...
@@ -322,6 +305,27 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra
322 305
return result;
323 306
}
324 307
308
+ private void logFix(GPSFixMoving next, final SpeedWithBearing nextSpeed) {
309
+ final boolean validityCached = next.isValidityCached();
310
+ final boolean validity;
311
+ if (validityCached) {
312
+ validity = next.isValidCached();
313
+ } else {
314
+ track.lockForRead();
315
+ try {
316
+ validity = track.isValid(next);
317
+ } finally {
318
+ track.unlockAfterRead();
319
+ }
320
+ }
321
+ // CSV logging: approxId, fixIndex, fixTimeMillis, validityCached, speedCached, COG, SOG
322
+ System.out.println(System.identityHashCode(this) + "," + next.getTimePoint().asMillis() + ","
323
+ + next.isValidityCached() + "," + next.isEstimatedSpeedCached() + ","
324
+ + (nextSpeed == null ? "null" : nextSpeed.getBearing().getDegrees()) + ","
325
+ + (nextSpeed == null ? "null" : nextSpeed.getKnots()) + ","
326
+ + validityCached + "," + validity);
327
+ }
328
+
325 329
/**
326 330
* Tries to extract a maneuver candidate from the current {@link #window}. See {@link #getManeuverCandidate()}.
327 331
* Basically, the maximum course change in the window has to be equal to or exceed the maneuver threshold. If
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
... ...
@@ -5,6 +5,18 @@
5 5
<div id="mainContent">
6 6
<h4 class="articleHeadline" id="sailingAnalyticsHeadline">What's New - Sailing Analytics</h4>
7 7
<div class="innerContent">
8
+ <h5 class="articleSubheadline">March 2026</h5>
9
+ <ul class="bulletList">
10
+ <li>Maneuver analysis now identifies track segments worth looking at with consistency.
11
+ Before, depending on fix arrival timing, analysis windows may have missed significant
12
+ course changes under special circumstances such as COG values changing in opposite
13
+ directions quickly.</li>
14
+ <li>Position fixes are considered for maneuver analysis now only after enough later fixes
15
+ (usually ten seconds) have been received to ensure that the older fixes don't change
16
+ their dampened COG/SOG values based on the newer fixes. This sacrifices short
17
+ maneuver calculation delays for better performance due to fewer re-calculations
18
+ and reliable results with no difference between live and replay analysis.</li>
19
+ </ul>
8 20
<h5 class="articleSubheadline">December 2025</h5>
9 21
<ul class="bulletList">
10 22
<li>Added boat class Fareast 28R.</li>
java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/util/PathGeneratorTracTracEval.java
... ...
@@ -5,10 +5,8 @@ import java.util.List;
5 5
6 6
import org.junit.jupiter.api.Assertions;
7 7
8
-import com.sap.sailing.domain.common.impl.MeterDistance;
9 8
import com.sap.sailing.simulator.Path;
10 9
import com.sap.sailing.simulator.impl.PathGeneratorTracTrac;
11
-import com.sap.sse.common.Distance;
12 10
13 11
public class PathGeneratorTracTracEval {
14 12
... ...
@@ -59,30 +57,25 @@ public class PathGeneratorTracTracEval {
59 57
}
60 58
61 59
public void testGetLegPolyline() {
62
-
63
- Distance maxDistance = new MeterDistance(4.88);
64
-
65 60
PathGeneratorTracTracEval.pathGenerator.setSelectionParameters(0, 0);
66
- Path legPolyline0 = PathGeneratorTracTracEval.pathGenerator.getPathPolyline(maxDistance);
61
+ Path legPolyline0 = PathGeneratorTracTracEval.pathGenerator.getPathPolyline();
67 62
Assertions.assertEquals(10, legPolyline0.getPathPoints().size());
68 63
69 64
PathGeneratorTracTracEval.pathGenerator.setSelectionParameters(1, 0);
70
- Path legPolyline1 = PathGeneratorTracTracEval.pathGenerator.getPathPolyline(maxDistance);
65
+ Path legPolyline1 = PathGeneratorTracTracEval.pathGenerator.getPathPolyline();
71 66
Assertions.assertEquals(7, legPolyline1.getPathPoints().size());
72 67
73 68
PathGeneratorTracTracEval.pathGenerator.setSelectionParameters(2, 0);
74
- Path legPolyline2 = PathGeneratorTracTracEval.pathGenerator.getPathPolyline(maxDistance);
69
+ Path legPolyline2 = PathGeneratorTracTracEval.pathGenerator.getPathPolyline();
75 70
Assertions.assertEquals(8, legPolyline2.getPathPoints().size());
76 71
77 72
PathGeneratorTracTracEval.pathGenerator.setSelectionParameters(3, 0);
78
- Path legPolyline3 = PathGeneratorTracTracEval.pathGenerator.getPathPolyline(maxDistance);
73
+ Path legPolyline3 = PathGeneratorTracTracEval.pathGenerator.getPathPolyline();
79 74
Assertions.assertEquals(7, legPolyline3.getPathPoints().size());
80 75
}
81 76
82 77
public void testGetLegsNames() {
83
-
84 78
List<String> legsNames = PathGeneratorTracTracEval.pathGenerator.getLegsNames();
85
-
86 79
Assertions.assertEquals(4, legsNames.size());
87 80
Assertions.assertEquals("G1 Start-Finish -> G1 Mark 1", legsNames.get(0));
88 81
Assertions.assertEquals("G1 Mark 1 -> G1 Mark 4", legsNames.get(1));
java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTracTrac.java
... ...
@@ -29,7 +29,6 @@ import com.sap.sailing.domain.tractracadapter.impl.TracTracAdapterFactoryImpl;
29 29
import com.sap.sailing.simulator.Path;
30 30
import com.sap.sailing.simulator.SimulationParameters;
31 31
import com.sap.sailing.simulator.TimedPositionWithSpeed;
32
-import com.sap.sse.common.Distance;
33 32
import com.sap.sse.common.TimePoint;
34 33
import com.sap.sse.common.impl.DegreeBearingImpl;
35 34
import com.sap.sse.common.impl.MillisecondsTimePoint;
... ...
@@ -176,7 +175,7 @@ public class PathGeneratorTracTrac extends PathGeneratorBase {
176 175
}
177 176
178 177
@SuppressWarnings("null")
179
- public Path getPathPolyline(Distance maxDistance) {
178
+ public Path getPathPolyline() {
180 179
this.intializeRaceHandle();
181 180
// getting the race
182 181
RaceDefinition raceDef = this.raceHandle.getRace();
java/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/data/transformer/CompleteManeuverCurveWithEstimationDataToManeuverForDataAnalysisTransformer.java
... ...
@@ -31,7 +31,7 @@ public class CompleteManeuverCurveWithEstimationDataToManeuverForDataAnalysisTra
31 31
if (maneuver != null) {
32 32
ManeuverForDataAnalysis maneuverForClassification = getManeuverForDataAnalysis(maneuver,
33 33
previousManeuver, nextManeuver, speedScalingDivisor,
34
- competitorTrackWithElementsToTransform.getBoatClass());
34
+ competitorTrackWithElementsToTransform.getBoatClass(), competitorTrackWithElementsToTransform.getCompetitorName());
35 35
if (maneuverForClassification != null) {
36 36
maneuversForClassification.add(maneuverForClassification);
37 37
}
... ...
@@ -41,7 +41,7 @@ public class CompleteManeuverCurveWithEstimationDataToManeuverForDataAnalysisTra
41 41
}
42 42
if (maneuver != null) {
43 43
ManeuverForDataAnalysis maneuverForClassification = getManeuverForDataAnalysis(maneuver, previousManeuver,
44
- null, speedScalingDivisor, competitorTrackWithElementsToTransform.getBoatClass());
44
+ null, speedScalingDivisor, competitorTrackWithElementsToTransform.getBoatClass(), competitorTrackWithElementsToTransform.getCompetitorName());
45 45
if (maneuverForClassification != null) {
46 46
maneuversForClassification.add(maneuverForClassification);
47 47
}
... ...
@@ -51,7 +51,7 @@ public class CompleteManeuverCurveWithEstimationDataToManeuverForDataAnalysisTra
51 51
52 52
private ManeuverForDataAnalysis getManeuverForDataAnalysis(CompleteManeuverCurveWithEstimationData maneuver,
53 53
CompleteManeuverCurveWithEstimationData previousManeuver,
54
- CompleteManeuverCurveWithEstimationData nextManeuver, double speedScalingDivisor, BoatClass boatClass) {
54
+ CompleteManeuverCurveWithEstimationData nextManeuver, double speedScalingDivisor, BoatClass boatClass, String competitorName) {
55 55
if (maneuver.getWind() == null) {
56 56
return null;
57 57
}
... ...
@@ -64,7 +64,7 @@ public class CompleteManeuverCurveWithEstimationDataToManeuverForDataAnalysisTra
64 64
new ConvertableManeuverForEstimationAdapterForCompleteManeuverCurveWithEstimationData(maneuver,
65 65
courseChangeInDegreesWithinTurningSectionOfPreviousManeuver,
66 66
courseChangeInDegreesWithinTurningSectionOfNextManeuver),
67
- speedScalingDivisor, boatClass);
67
+ speedScalingDivisor, boatClass, competitorName);
68 68
ManeuverTypeForDataAnalysis maneuverType = getManeuverTypeForDataAnalysis(maneuver);
69 69
double absoluteTotalCourseChangeInDegrees = Math.abs(maneuverForEstimation.getCourseChangeInDegrees());
70 70
double absoluteTotalCourseChangeWithinMainCurveInDegrees = Math
java/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/data/transformer/LabeledManeuverForEstimationTransformer.java
... ...
@@ -20,7 +20,7 @@ public class LabeledManeuverForEstimationTransformer implements
20 20
ConvertableToLabeledManeuverForEstimation nextManeuver, double speedScalingDivisor, BoatClass boatClass,
21 21
String regattaName, String competitorName) {
22 22
ManeuverForEstimation maneuverForEstimation = internalTransformer.getManeuverForEstimation(maneuver,
23
- speedScalingDivisor, boatClass);
23
+ speedScalingDivisor, boatClass, competitorName);
24 24
ManeuverTypeForClassification maneuverType = getManeuverTypeForClassification(maneuver);
25 25
LabeledManeuverForEstimation labelledManeuverForEstimation = new LabeledManeuverForEstimation(
26 26
maneuverForEstimation.getManeuverTimePoint(), maneuverForEstimation.getManeuverPosition(),
java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/integration/IncrementalMstHmmWindEstimationForTrackedRaceTest.java
... ...
@@ -7,8 +7,6 @@ import java.io.File;
7 7
import java.io.IOException;
8 8
import java.net.URI;
9 9
import java.net.URL;
10
-import java.nio.charset.StandardCharsets;
11
-import java.security.MessageDigest;
12 10
import java.text.SimpleDateFormat;
13 11
import java.util.ArrayList;
14 12
import java.util.Collections;
... ...
@@ -140,18 +138,8 @@ public class IncrementalMstHmmWindEstimationForTrackedRaceTest extends OnlineTra
140 138
if (polarDataBearerToken == null) {
141 139
logger.warning("Couldn't find POLAR_DATA_BEARER_TOKEN environment variable either, polar data service will not be available");
142 140
} else {
143
- final byte[] digest = MessageDigest.getInstance("SHA-256").digest(polarDataBearerToken.getBytes(StandardCharsets.UTF_8));
144
- final StringBuilder hexString = new StringBuilder();
145
- for (byte b : digest) {
146
- String hex = Integer.toHexString(0xff & b);
147
- if (hex.length() == 1) {
148
- hexString.append('0');
149
- }
150
- hexString.append(hex);
151
- }
152 141
logger.info("Found POLAR_DATA_BEARER_TOKEN environment variable, length "+polarDataBearerToken.length()
153
- +", SHA256 hash "+hexString.toString()
154
- +"; polar data service will be available");
142
+ +"; polar data service will be available");
155 143
}
156 144
} else {
157 145
logger.info("Found polardata.source.bearertoken system property, polar data service will be available");
java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/integration/IncrementalMstManeuverGraphGeneratorTest.java
... ...
@@ -8,8 +8,6 @@ import java.net.MalformedURLException;
8 8
import java.net.URI;
9 9
import java.net.URISyntaxException;
10 10
import java.net.URL;
11
-import java.nio.charset.StandardCharsets;
12
-import java.security.MessageDigest;
13 11
import java.security.NoSuchAlgorithmException;
14 12
import java.text.SimpleDateFormat;
15 13
import java.util.ArrayList;
... ...
@@ -104,17 +102,7 @@ public class IncrementalMstManeuverGraphGeneratorTest extends OnlineTracTracBase
104 102
if (polarDataBearerToken == null) {
105 103
logger.warning("Couldn't find POLAR_DATA_BEARER_TOKEN environment variable either, polar data service will not be available");
106 104
} else {
107
- final byte[] digest = MessageDigest.getInstance("SHA-256").digest(polarDataBearerToken.getBytes(StandardCharsets.UTF_8));
108
- final StringBuilder hexString = new StringBuilder();
109
- for (byte b : digest) {
110
- String hex = Integer.toHexString(0xff & b);
111
- if (hex.length() == 1) {
112
- hexString.append('0');
113
- }
114
- hexString.append(hex);
115
- }
116 105
logger.info("Found POLAR_DATA_BEARER_TOKEN environment variable, length "+polarDataBearerToken.length()
117
- +", SHA256 hash "+hexString.toString()
118 106
+"; polar data service will be available");
119 107
}
120 108
} else {
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/data/transformer/CompleteManeuverCurveWithEstimationDataToManeuverForEstimationTransformer.java
... ...
@@ -24,7 +24,6 @@ public class CompleteManeuverCurveWithEstimationDataToManeuverForEstimationTrans
24 24
List<ConvertableToLabeledManeuverForEstimation> convertableManeuvers = ConvertableManeuverForEstimationAdapterForCompleteManeuverCurveWithEstimationData
25 25
.getConvertableManeuvers(competitorTrackWithElementsToTransform.getElements());
26 26
return internalTransformer.getManeuversForEstimation(convertableManeuvers,
27
- competitorTrackWithElementsToTransform.getBoatClass());
27
+ competitorTrackWithElementsToTransform.getBoatClass(), competitorTrackWithElementsToTransform.getCompetitorName());
28 28
}
29
-
30 29
}
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/data/transformer/ManeuverForEstimationTransformer.java
... ...
@@ -4,7 +4,6 @@ import java.util.ArrayList;
4 4
import java.util.List;
5 5
6 6
import com.sap.sailing.domain.base.BoatClass;
7
-import com.sap.sailing.domain.base.Competitor;
8 7
import com.sap.sailing.windestimation.data.CompetitorTrackWithEstimationData;
9 8
import com.sap.sailing.windestimation.data.ManeuverCategory;
10 9
import com.sap.sailing.windestimation.data.ManeuverForEstimation;
... ...
@@ -24,12 +23,12 @@ public class ManeuverForEstimationTransformer
24 23
public static final int MIN_SECONDS_TO_OTHER_MANEUVER = 4;
25 24
26 25
public List<ManeuverForEstimation> getManeuversForEstimation(
27
- List<? extends ConvertableToManeuverForEstimation> convertableManeuvers, BoatClass boatClass) {
26
+ List<? extends ConvertableToManeuverForEstimation> convertableManeuvers, BoatClass boatClass, String competitorName) {
28 27
double speedScalingDivisor = getSpeedScalingDivisor(convertableManeuvers);
29 28
List<ManeuverForEstimation> maneuversForEstimation = new ArrayList<>();
30 29
for (ConvertableToManeuverForEstimation maneuver : convertableManeuvers) {
31 30
ManeuverForEstimation maneuverForEstimation = getManeuverForEstimation(maneuver, speedScalingDivisor,
32
- boatClass);
31
+ boatClass, competitorName);
33 32
maneuversForEstimation.add(maneuverForEstimation);
34 33
}
35 34
return maneuversForEstimation;
... ...
@@ -150,12 +149,7 @@ public class ManeuverForEstimationTransformer
150 149
}
151 150
152 151
public ManeuverForEstimation getManeuverForEstimation(ConvertableToManeuverForEstimation maneuver,
153
- double speedScalingDivisor, BoatClass boatClass) {
154
- return getManeuverForEstimation(maneuver, speedScalingDivisor, boatClass, null);
155
- }
156
-
157
- public ManeuverForEstimation getManeuverForEstimation(ConvertableToManeuverForEstimation maneuver,
158
- double speedScalingDivisor, BoatClass boatClass, Competitor competitor) {
152
+ double speedScalingDivisor, BoatClass boatClass, String competitorName) {
159 153
ManeuverCategory maneuverCategory = getManeuverCategory(maneuver);
160 154
double speedLossRatio = maneuver.getSpeedWithBearingBefore().getKnots() > 0
161 155
? maneuver.getLowestSpeed().getKnots() / maneuver.getSpeedWithBearingBefore().getKnots()
... ...
@@ -182,7 +176,7 @@ public class ManeuverForEstimationTransformer
182 176
deviationFromOptimalTackAngleInDegrees, deviationFromOptimalJibeAngleInDegrees, speedLossRatio,
183 177
speedGainRatio, lowestSpeedVsExitingSpeedRatio, clean, maneuverCategory, scaledSpeedBeforeInKnots,
184 178
scaledSpeedAfterInKnots, maneuver.isMarkPassing(), boatClass, markPassingDataAvailable,
185
- competitor==null?null:competitor.getName());
179
+ competitorName);
186 180
return maneuverForEstimation;
187 181
}
188 182
... ...
@@ -190,7 +184,7 @@ public class ManeuverForEstimationTransformer
190 184
public List<ManeuverForEstimation> transformElements(
191 185
CompetitorTrackWithEstimationData<ConvertableToManeuverForEstimation> competitorTrackWithElementsToTransform) {
192 186
return getManeuversForEstimation(competitorTrackWithElementsToTransform.getElements(),
193
- competitorTrackWithElementsToTransform.getBoatClass());
187
+ competitorTrackWithElementsToTransform.getBoatClass(), competitorTrackWithElementsToTransform.getCompetitorName());
194 188
}
195 189
196 190
}
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/CompleteManeuverCurveToManeuverForEstimationConverter.java
... ...
@@ -111,7 +111,7 @@ public class CompleteManeuverCurveToManeuverForEstimationConverter {
111 111
// TODO compute scaledSpeedDivisor, recompute maneuverForEstimation, reclassify all maneuver instances if
112 112
// scaledSpeedDivisor has significantly changed?
113 113
ManeuverForEstimation maneuverForEstimation = maneuverForEstimationTransformer
114
- .getManeuverForEstimation(convertableManeuver, 1.0, boatClass, competitor);
114
+ .getManeuverForEstimation(convertableManeuver, 1.0, boatClass, competitor.getName());
115 115
return maneuverForEstimation;
116 116
}
117 117
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/integration/IncrementalMstHmmWindEstimationForTrackedRace.java
... ...
@@ -186,9 +186,11 @@ public class IncrementalMstHmmWindEstimationForTrackedRace implements Incrementa
186 186
}
187 187
graphComponents = mstManeuverGraphGenerator.parseGraph();
188 188
if (logger.isLoggable(Level.FINE)) {
189
- logger.fine("Exporting the maneuver graph to file after updating it with new maneuver spots for competitor "+competitor);
190 189
try {
191
- MstGraphExportHelper.exportToFile(graphComponents, mstManeuverGraphGenerator.getTransitionProbabilitiesCalculator(), File.createTempFile("maneuverExport_", ".json").getCanonicalPath());
190
+ final String canonicalTmpPath = File.createTempFile("maneuverExport_", ".json").getCanonicalPath();
191
+ logger.fine("Exporting the maneuver graph to file after updating it with new maneuver spots for competitor "+competitor
192
+ +"; visualize by running com.sap.sailing.windestimation.lab/python/mst_graph_visualizer_graphviz.py "+canonicalTmpPath+" output.pdf");
193
+ MstGraphExportHelper.exportToFile(graphComponents, mstManeuverGraphGenerator.getTransitionProbabilitiesCalculator(), canonicalTmpPath);
192 194
} catch (IOException e) {
193 195
logger.log(Level.WARNING, "Exporting the maneuver graph to file failed", e);
194 196
}
java/target/configuration/logging_debug.properties
... ...
@@ -66,5 +66,6 @@ com.sap.sailing.aiagent.impl.RaceListener.level = FINE
66 66
# Show GithubReleasesRepository log output
67 67
com.sap.sse.landscape.impl.GithubReleasesRepository.level = FINE
68 68
69
-com.sap.sailing.windestimation.integration.IncrementalMstHmmWindEstimationForTrackedRaceTest.level = FINE
70
-com.sap.sailing.windestimation.integration.IncrementalMstHmmWindEstimationForTrackedRace.level = FINE
... ...
\ No newline at end of file
0
+# Produce wind-from-maneuver estimation graph:
1
+#com.sap.sailing.windestimation.integration.IncrementalMstHmmWindEstimationForTrackedRaceTest.level = FINE
2
+#com.sap.sailing.windestimation.integration.IncrementalMstHmmWindEstimationForTrackedRace.level = FINE
... ...
\ No newline at end of file