java/com.sap.sailing.windestimation.lab/python/mst_graph_visualizer_graphviz.py
... ...
@@ -117,6 +117,14 @@ def create_node_label_with_ports(node, best_type=None):
117 117
# Get distance/time to parent (pass whole node for full info)
118 118
dist_str = format_distance(node)
119 119
120
+ # Get competitor info
121
+ competitor_name = node.get('competitorName', '')
122
+ # Truncate long names for display
123
+ if competitor_name:
124
+ competitor_str = competitor_name[:12] + '...' if len(competitor_name) > 15 else competitor_name
125
+ else:
126
+ competitor_str = ''
127
+
120 128
# Build HTML table with PORT attributes
121 129
html = '<<TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">'
122 130
... ...
@@ -158,11 +166,14 @@ def create_node_label_with_ports(node, best_type=None):
158 166
html += f'<TD PORT="{port_name}"{border_attr}>{cell_content}</TD>'
159 167
html += '</TR>'
160 168
161
- # Footer row with timestamp/distance - spans all columns
169
+ # Footer row with competitor info, timestamp and distance - spans all columns
170
+ footer_parts = []
171
+ if competitor_str:
172
+ footer_parts.append(f'<FONT COLOR="purple"><B>{competitor_str}</B></FONT>')
173
+ footer_parts.append(time_part)
162 174
if dist_str:
163
- footer_content = f'{time_part} <FONT COLOR="blue">↑{dist_str}</FONT>'
164
- else:
165
- footer_content = time_part
175
+ footer_parts.append(f'<FONT COLOR="blue">↑{dist_str}</FONT>')
176
+ footer_content = ' '.join(footer_parts)
166 177
html += f'<TR><TD COLSPAN="4" BGCOLOR="white"><FONT POINT-SIZE="9">{footer_content}</FONT></TD></TR>'
167 178
168 179
# Bottom row with path vote diagnostics AND ports for OUTGOING edges
java/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/data/LabeledManeuverForEstimation.java
... ...
@@ -21,12 +21,12 @@ public class LabeledManeuverForEstimation extends ManeuverForEstimation {
21 21
double lowestSpeedVsExitingSpeedRatio, boolean clean, ManeuverCategory maneuverCategory,
22 22
double scaledSpeedBefore, double scaledSpeedAfter, boolean markPassing, BoatClass boatClass,
23 23
boolean markPassingDataAvailable, ManeuverTypeForClassification maneuverType, Wind wind,
24
- String regattaName) {
24
+ String regattaName, String competitorName) {
25 25
super(maneuverTimePoint, maneuverPosition, middleCourse, speedWithBearingBefore, speedWithBearingAfter,
26 26
courseChangeInDegrees, courseChangeWithinMainCurveInDegrees, maxTurningRateInDegreesPerSecond,
27 27
deviationFromOptimalTackAngleInDegrees, deviationFromOptimalJibeAngleInDegrees, speedLossRatio,
28 28
speedGainRatio, lowestSpeedVsExitingSpeedRatio, clean, maneuverCategory, scaledSpeedBefore,
29
- scaledSpeedAfter, markPassing, boatClass, markPassingDataAvailable);
29
+ scaledSpeedAfter, markPassing, boatClass, markPassingDataAvailable, competitorName);
30 30
this.maneuverType = maneuverType;
31 31
this.wind = wind;
32 32
this.regattaName = regattaName;
java/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/data/serialization/LabeledManeuverForEstimationJsonDeserializer.java
... ...
@@ -62,6 +62,7 @@ public class LabeledManeuverForEstimationJsonDeserializer implements JsonDeseria
62 62
Double windSpeedInKnots = (Double) object.get(LabeledManeuverForEstimationJsonSerializer.WIND_SPEED);
63 63
Double windCourse = (Double) object.get(LabeledManeuverForEstimationJsonSerializer.WIND_COURSE);
64 64
String regattaName = (String) object.get(LabeledManeuverForEstimationJsonSerializer.REGATTA_NAME);
65
+ String competitorName = (String) object.get(LabeledManeuverForEstimationJsonSerializer.COMPETITOR_NAME);
65 66
MillisecondsTimePoint maneuverTimePoint = new MillisecondsTimePoint(maneuverTimePointMillis);
66 67
DegreePosition maneuverPosition = new DegreePosition(positionLatitude, positionLongitude);
67 68
LabeledManeuverForEstimation maneuver = new LabeledManeuverForEstimation(maneuverTimePoint, maneuverPosition,
... ...
@@ -74,7 +75,7 @@ public class LabeledManeuverForEstimationJsonDeserializer implements JsonDeseria
74 75
markPassingDataAvailable, maneuverType,
75 76
new WindImpl(maneuverPosition, maneuverTimePoint,
76 77
new KnotSpeedWithBearingImpl(windSpeedInKnots, new DegreeBearingImpl(windCourse))),
77
- regattaName);
78
+ regattaName, competitorName);
78 79
return maneuver;
79 80
}
80 81
java/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/data/serialization/LabeledManeuverForEstimationJsonSerializer.java
... ...
@@ -36,6 +36,7 @@ public class LabeledManeuverForEstimationJsonSerializer implements JsonSerialize
36 36
public static final String BOAT_CLASS = "boatClass";
37 37
public static final String MARK_PASSING_DATA_AVAILABLE = "markPassingDataAvailable";
38 38
public static final String REGATTA_NAME = "regattaName";
39
+ public static final String COMPETITOR_NAME = "competitorName";
39 40
40 41
private final BoatClassJsonSerializer boatClassSerializer = new DetailedBoatClassJsonSerializer();
41 42
... ...
@@ -69,6 +70,7 @@ public class LabeledManeuverForEstimationJsonSerializer implements JsonSerialize
69 70
json.put(WIND_SPEED, maneuver.getWind().getKnots());
70 71
json.put(WIND_COURSE, maneuver.getWind().getBearing().getDegrees());
71 72
json.put(REGATTA_NAME, maneuver.getRegattaName());
73
+ json.put(COMPETITOR_NAME, maneuver.getCompetitorName());
72 74
return json;
73 75
}
74 76
java/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/data/transformer/CompleteManeuverCurveWithEstimationDataToLabelledManeuverForEstimationTransformer.java
... ...
@@ -18,7 +18,7 @@ public class CompleteManeuverCurveWithEstimationDataToLabelledManeuverForEstimat
18 18
.getConvertableManeuvers(competitorTrackWithElementsToTransform.getElements());
19 19
return internalTransformer.getManeuversForEstimation(convertableManeuvers,
20 20
competitorTrackWithElementsToTransform.getBoatClass(),
21
- competitorTrackWithElementsToTransform.getRegattaName());
21
+ competitorTrackWithElementsToTransform.getRegattaName(), competitorTrackWithElementsToTransform.getCompetitorName());
22 22
}
23 23
24 24
}
java/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/data/transformer/LabeledManeuverForEstimationTransformer.java
... ...
@@ -18,7 +18,7 @@ public class LabeledManeuverForEstimationTransformer implements
18 18
public LabeledManeuverForEstimation getManeuverForEstimation(ConvertableToLabeledManeuverForEstimation maneuver,
19 19
ConvertableToLabeledManeuverForEstimation previousManeuver,
20 20
ConvertableToLabeledManeuverForEstimation nextManeuver, double speedScalingDivisor, BoatClass boatClass,
21
- String regattaName) {
21
+ String regattaName, String competitorName) {
22 22
ManeuverForEstimation maneuverForEstimation = internalTransformer.getManeuverForEstimation(maneuver,
23 23
speedScalingDivisor, boatClass);
24 24
ManeuverTypeForClassification maneuverType = getManeuverTypeForClassification(maneuver);
... ...
@@ -35,7 +35,7 @@ public class LabeledManeuverForEstimationTransformer implements
35 35
maneuverForEstimation.getManeuverCategory(), maneuverForEstimation.getScaledSpeedBefore(),
36 36
maneuverForEstimation.getScaledSpeedAfter(), maneuverForEstimation.isMarkPassing(),
37 37
maneuverForEstimation.getBoatClass(), maneuverForEstimation.isMarkPassingDataAvailable(), maneuverType,
38
- maneuver.getWind(), regattaName);
38
+ maneuver.getWind(), regattaName, competitorName);
39 39
return labelledManeuverForEstimation;
40 40
}
41 41
... ...
@@ -60,7 +60,7 @@ public class LabeledManeuverForEstimationTransformer implements
60 60
61 61
public List<LabeledManeuverForEstimation> getManeuversForEstimation(
62 62
List<ConvertableToLabeledManeuverForEstimation> convertableManeuvers, BoatClass boatClass,
63
- String regattaName) {
63
+ String regattaName, String competitorName) {
64 64
double speedScalingDivisor = internalTransformer.getSpeedScalingDivisor(convertableManeuvers);
65 65
List<LabeledManeuverForEstimation> maneuversForEstimation = new ArrayList<>();
66 66
ConvertableToLabeledManeuverForEstimation previousManeuver = null;
... ...
@@ -68,7 +68,7 @@ public class LabeledManeuverForEstimationTransformer implements
68 68
for (ConvertableToLabeledManeuverForEstimation nextManeuver : convertableManeuvers) {
69 69
if (maneuver != null) {
70 70
LabeledManeuverForEstimation maneuverForEstimation = getManeuverForEstimation(maneuver,
71
- previousManeuver, nextManeuver, speedScalingDivisor, boatClass, regattaName);
71
+ previousManeuver, nextManeuver, speedScalingDivisor, boatClass, regattaName, competitorName);
72 72
if (maneuverForEstimation != null) {
73 73
maneuversForEstimation.add(maneuverForEstimation);
74 74
}
... ...
@@ -78,7 +78,7 @@ public class LabeledManeuverForEstimationTransformer implements
78 78
}
79 79
if (maneuver != null) {
80 80
LabeledManeuverForEstimation maneuverForEstimation = getManeuverForEstimation(maneuver, previousManeuver,
81
- null, speedScalingDivisor, boatClass, regattaName);
81
+ null, speedScalingDivisor, boatClass, regattaName, competitorName);
82 82
if (maneuverForEstimation != null) {
83 83
maneuversForEstimation.add(maneuverForEstimation);
84 84
}
... ...
@@ -91,7 +91,7 @@ public class LabeledManeuverForEstimationTransformer implements
91 91
CompetitorTrackWithEstimationData<ConvertableToLabeledManeuverForEstimation> competitorTrackWithElementsToTransform) {
92 92
return getManeuversForEstimation(competitorTrackWithElementsToTransform.getElements(),
93 93
competitorTrackWithElementsToTransform.getBoatClass(),
94
- competitorTrackWithElementsToTransform.getRegattaName());
94
+ competitorTrackWithElementsToTransform.getRegattaName(), competitorTrackWithElementsToTransform.getCompetitorName());
95 95
}
96 96
97 97
}
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/MstGraphExporter.java
... ...
@@ -16,7 +16,6 @@ import com.sap.sailing.windestimation.aggregator.graph.DijkstraShortestPathFinde
16 16
import com.sap.sailing.windestimation.aggregator.graph.DijsktraShortestPathFinder;
17 17
import com.sap.sailing.windestimation.aggregator.graph.ElementAdjacencyQualityMetric;
18 18
import com.sap.sailing.windestimation.aggregator.graph.InnerGraphSuccessorSupplier;
19
-import com.sap.sailing.windestimation.aggregator.hmm.GraphLevelInference;
20 19
import com.sap.sailing.windestimation.aggregator.hmm.GraphNode;
21 20
import com.sap.sailing.windestimation.aggregator.hmm.WindCourseRange;
22 21
import com.sap.sailing.windestimation.aggregator.msthmm.MstManeuverGraphGenerator.MstManeuverGraphComponents;
... ...
@@ -104,6 +103,10 @@ public class MstGraphExporter {
104 103
writer.write(" \"id\": " + nodeId + ",\n");
105 104
writer.write(" \"depth\": " + depth + ",\n");
106 105
writer.write(" \"timestamp\": \"" + DATE_FORMAT.format(maneuver.getManeuverTimePoint().asDate()) + "\",\n");
106
+ // Competitor info
107
+ if (maneuver.getCompetitorName() != null) {
108
+ writer.write(" \"competitorName\": \"" + escapeJson(maneuver.getCompetitorName()) + "\",\n");
109
+ }
107 110
writer.write(" \"position\": {\"lat\": " + maneuver.getManeuverPosition().getLatDeg() +
108 111
", \"lon\": " + maneuver.getManeuverPosition().getLngDeg() + "},\n");
109 112
// distanceToParent is actually the "compound distance" = sum of two predicted standard deviations
... ...
@@ -343,4 +346,32 @@ public class MstGraphExporter {
343 346
assignNodeIds(child, levelToId, nodeIdCounter);
344 347
}
345 348
}
349
+
350
+ /**
351
+ * Escapes special characters in a string for safe JSON encoding.
352
+ */
353
+ private static String escapeJson(String s) {
354
+ if (s == null) {
355
+ return null;
356
+ }
357
+ StringBuilder sb = new StringBuilder();
358
+ for (char c : s.toCharArray()) {
359
+ switch (c) {
360
+ case '"': sb.append("\\\""); break;
361
+ case '\\': sb.append("\\\\"); break;
362
+ case '\b': sb.append("\\b"); break;
363
+ case '\f': sb.append("\\f"); break;
364
+ case '\n': sb.append("\\n"); break;
365
+ case '\r': sb.append("\\r"); break;
366
+ case '\t': sb.append("\\t"); break;
367
+ default:
368
+ if (c < ' ') {
369
+ sb.append(String.format("\\u%04x", (int) c));
370
+ } else {
371
+ sb.append(c);
372
+ }
373
+ }
374
+ }
375
+ return sb.toString();
376
+ }
346 377
}
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/data/ManeuverForEstimation.java
... ...
@@ -36,6 +36,7 @@ public class ManeuverForEstimation implements Comparable<ManeuverForEstimation>
36 36
private final boolean markPassing;
37 37
private final BoatClass boatClass;
38 38
private final boolean markPassingDataAvailable;
39
+ private final String competitorName;
39 40
40 41
public ManeuverForEstimation(TimePoint maneuverTimePoint, Position maneuverPosition, Bearing middleCourse,
41 42
SpeedWithBearing speedWithBearingBefore, SpeedWithBearing speedWithBearingAfter,
... ...
@@ -44,7 +45,7 @@ public class ManeuverForEstimation implements Comparable<ManeuverForEstimation>
44 45
Double deviationFromOptimalJibeAngleInDegrees, double speedLossRatio, double speedGainRatio,
45 46
double lowestSpeedVsExitingSpeedRatio, boolean clean, ManeuverCategory maneuverCategory,
46 47
double scaledSpeedBefore, double scaledSpeedAfter, boolean markPassing, BoatClass boatClass,
47
- boolean markPassingDataAvailable) {
48
+ boolean markPassingDataAvailable, String competitorName) {
48 49
this.maneuverTimePoint = maneuverTimePoint;
49 50
this.maneuverPosition = maneuverPosition;
50 51
this.middleCourse = middleCourse;
... ...
@@ -65,6 +66,7 @@ public class ManeuverForEstimation implements Comparable<ManeuverForEstimation>
65 66
this.markPassing = markPassing;
66 67
this.boatClass = boatClass;
67 68
this.markPassingDataAvailable = markPassingDataAvailable;
69
+ this.competitorName = competitorName;
68 70
}
69 71
70 72
public TimePoint getManeuverTimePoint() {
... ...
@@ -147,6 +149,10 @@ public class ManeuverForEstimation implements Comparable<ManeuverForEstimation>
147 149
return markPassingDataAvailable;
148 150
}
149 151
152
+ public String getCompetitorName() {
153
+ return competitorName;
154
+ }
155
+
150 156
@Override
151 157
public int compareTo(ManeuverForEstimation o) {
152 158
return maneuverTimePoint.compareTo(o.maneuverTimePoint);
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/data/transformer/ManeuverForEstimationTransformer.java
... ...
@@ -4,6 +4,7 @@ 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;
7 8
import com.sap.sailing.windestimation.data.CompetitorTrackWithEstimationData;
8 9
import com.sap.sailing.windestimation.data.ManeuverCategory;
9 10
import com.sap.sailing.windestimation.data.ManeuverForEstimation;
... ...
@@ -150,6 +151,11 @@ public class ManeuverForEstimationTransformer
150 151
151 152
public ManeuverForEstimation getManeuverForEstimation(ConvertableToManeuverForEstimation maneuver,
152 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) {
153 159
ManeuverCategory maneuverCategory = getManeuverCategory(maneuver);
154 160
double speedLossRatio = maneuver.getSpeedWithBearingBefore().getKnots() > 0
155 161
? maneuver.getLowestSpeed().getKnots() / maneuver.getSpeedWithBearingBefore().getKnots()
... ...
@@ -175,7 +181,8 @@ public class ManeuverForEstimationTransformer
175 181
maneuver.getCourseChangeInDegreesWithinTurningSection(), maneuver.getMaxTurningRateInDegreesPerSecond(),
176 182
deviationFromOptimalTackAngleInDegrees, deviationFromOptimalJibeAngleInDegrees, speedLossRatio,
177 183
speedGainRatio, lowestSpeedVsExitingSpeedRatio, clean, maneuverCategory, scaledSpeedBeforeInKnots,
178
- scaledSpeedAfterInKnots, maneuver.isMarkPassing(), boatClass, markPassingDataAvailable);
184
+ scaledSpeedAfterInKnots, maneuver.isMarkPassing(), boatClass, markPassingDataAvailable,
185
+ competitor.getName());
179 186
return maneuverForEstimation;
180 187
}
181 188
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);
114
+ .getManeuverForEstimation(convertableManeuver, 1.0, boatClass, competitor);
115 115
return maneuverForEstimation;
116 116
}
117 117