c90b3ab5b05bae7d3cd67cd115bab4c8d97d4e88
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 |