1c53e0ba6b4ccac7706c5a1e0b9295a3fccc82f2
java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/MstGraphExporter.java
| ... | ... | @@ -8,10 +8,6 @@ import java.util.HashSet; |
| 8 | 8 | import java.util.Map; |
| 9 | 9 | import java.util.Set; |
| 10 | 10 | |
| 11 | -import com.sap.sailing.windestimation.aggregator.graph.DijkstraShortestPathFinderImpl; |
|
| 12 | -import com.sap.sailing.windestimation.aggregator.graph.DijsktraShortestPathFinder; |
|
| 13 | -import com.sap.sailing.windestimation.aggregator.graph.ElementAdjacencyQualityMetric; |
|
| 14 | -import com.sap.sailing.windestimation.aggregator.graph.InnerGraphSuccessorSupplier; |
|
| 15 | 11 | import com.sap.sailing.windestimation.aggregator.hmm.GraphLevelInference; |
| 16 | 12 | import com.sap.sailing.windestimation.aggregator.hmm.GraphNode; |
| 17 | 13 | import com.sap.sailing.windestimation.aggregator.hmm.WindCourseRange; |
| ... | ... | @@ -19,8 +15,6 @@ import com.sap.sailing.windestimation.aggregator.msthmm.MstManeuverGraphGenerato |
| 19 | 15 | import com.sap.sailing.windestimation.data.ManeuverForEstimation; |
| 20 | 16 | import com.sap.sailing.windestimation.data.ManeuverTypeForClassification; |
| 21 | 17 | |
| 22 | -import java.util.function.Supplier; |
|
| 23 | - |
|
| 24 | 18 | /** |
| 25 | 19 | * Exports MST graph data to JSON format for visualization. |
| 26 | 20 | * The output can be consumed by a Python visualization script. |
| ... | ... | @@ -44,22 +38,31 @@ public class MstGraphExporter { |
| 44 | 38 | * @throws IOException if writing fails |
| 45 | 39 | */ |
| 46 | 40 | public void exportToJson(MstManeuverGraphComponents graphComponents, Writer writer) throws IOException { |
| 47 | - // Collect all best paths for highlighting |
|
| 48 | - final Set<String> bestPathEdges = collectBestPathEdges(graphComponents); |
|
| 49 | - final Map<String, String> bestNodePerLevel = collectBestNodePerLevel(graphComponents); |
|
| 41 | + // First, assign node IDs and collect best nodes per level |
|
| 42 | + final MstGraphLevel root = graphComponents.getRoot(); |
|
| 43 | + final Map<MstGraphLevel, Integer> levelToId = new HashMap<>(); |
|
| 44 | + final int[] nodeIdCounter = {0}; |
|
| 45 | + assignNodeIds(root, levelToId, nodeIdCounter); |
|
| 46 | + |
|
| 47 | + // Collect best (disambiguated) classification per node |
|
| 48 | + final Map<String, String> bestNodePerLevel = collectBestNodePerLevel(graphComponents, levelToId); |
|
| 49 | + |
|
| 50 | + // Derive best path edges from the best node selections |
|
| 51 | + // This ensures edges connect nodes with red frames (best classifications) |
|
| 52 | + final Set<String> bestPathEdges = collectBestPathEdges(graphComponents, bestNodePerLevel, levelToId); |
|
| 53 | + |
|
| 50 | 54 | writer.write("{\n"); |
| 51 | 55 | writer.write(" \"nodes\": [\n"); |
| 52 | 56 | // Export all nodes starting from root |
| 53 | - final MstGraphLevel root = graphComponents.getRoot(); |
|
| 54 | 57 | final boolean[] firstNode = {true}; |
| 55 | - final Map<MstGraphLevel, Integer> levelToId = new HashMap<>(); |
|
| 56 | - final int[] nodeIdCounter = {0}; |
|
| 57 | - exportNode(root, writer, firstNode, levelToId, nodeIdCounter, 0); |
|
| 58 | + final int[] exportNodeIdCounter = {0}; |
|
| 59 | + final Map<MstGraphLevel, Integer> exportLevelToId = new HashMap<>(); |
|
| 60 | + exportNode(root, writer, firstNode, exportLevelToId, exportNodeIdCounter, 0); |
|
| 58 | 61 | writer.write("\n ],\n"); |
| 59 | 62 | writer.write(" \"edges\": [\n"); |
| 60 | 63 | // Export edges between nodes |
| 61 | 64 | final boolean[] firstEdge = {true}; |
| 62 | - exportEdges(root, writer, firstEdge, levelToId, bestPathEdges); |
|
| 65 | + exportEdges(root, writer, firstEdge, exportLevelToId, bestPathEdges); |
|
| 63 | 66 | writer.write("\n ],\n"); |
| 64 | 67 | // Export best path information |
| 65 | 68 | writer.write(" \"bestPaths\": {\n"); |
| ... | ... | @@ -178,54 +181,43 @@ public class MstGraphExporter { |
| 178 | 181 | } |
| 179 | 182 | } |
| 180 | 183 | |
| 181 | - private Set<String> collectBestPathEdges(MstManeuverGraphComponents graphComponents) { |
|
| 182 | - final Set<String> bestPathEdges = new HashSet<>(); |
|
| 183 | - final Map<MstGraphLevel, Integer> levelToId = new HashMap<>(); |
|
| 184 | - final int[] nodeIdCounter = {0}; |
|
| 185 | - assignNodeIds(graphComponents.getRoot(), levelToId, nodeIdCounter); |
|
| 186 | - final ElementAdjacencyQualityMetric<GraphNode<MstGraphLevel>> edgeQualityMetric = (previousNode, currentNode) -> { |
|
| 187 | - return transitionProbabilitiesCalculator.getTransitionProbability(currentNode, previousNode, |
|
| 188 | - previousNode.getGraphLevel() == null ? 0.0 : previousNode.getGraphLevel().getDistanceToParent()); |
|
| 189 | - }; |
|
| 190 | - for (final MstGraphLevel leaf : graphComponents.getLeaves()) { |
|
| 191 | - final InnerGraphSuccessorSupplier<GraphNode<MstGraphLevel>, MstGraphLevel> innerGraphSuccessorSupplier = |
|
| 192 | - new InnerGraphSuccessorSupplier<>(graphComponents, |
|
| 193 | - (final Supplier<String> nameSupplier) -> new GraphNode<MstGraphLevel>( |
|
| 194 | - null, null, new WindCourseRange(0, 360), 1.0, 0, null) { |
|
| 195 | - @Override |
|
| 196 | - public String toString() { |
|
| 197 | - return nameSupplier.get(); |
|
| 198 | - } |
|
| 199 | - }); |
|
| 200 | - final DijsktraShortestPathFinder<GraphNode<MstGraphLevel>> dijkstra = |
|
| 201 | - new DijkstraShortestPathFinderImpl<>( |
|
| 202 | - innerGraphSuccessorSupplier.getArtificialLeaf(leaf), |
|
| 203 | - innerGraphSuccessorSupplier.getArtificialRoot(), |
|
| 204 | - innerGraphSuccessorSupplier, edgeQualityMetric); |
|
| 205 | - GraphNode<MstGraphLevel> prev = null; |
|
| 206 | - for (final GraphNode<MstGraphLevel> node : dijkstra.getShortestPath()) { |
|
| 207 | - if (prev != null && prev.getGraphLevel() != null && node.getGraphLevel() != null) { |
|
| 208 | - Integer prevId = levelToId.get(prev.getGraphLevel()); |
|
| 209 | - Integer nodeId = levelToId.get(node.getGraphLevel()); |
|
| 210 | - if (prevId != null && nodeId != null) { |
|
| 211 | - // Dijkstra goes from leaf to root (child to parent) |
|
| 212 | - // We export edges from parent to child, so store the edge in that direction |
|
| 213 | - String edgeKey = nodeId + "_" + node.getManeuverType().ordinal() + |
|
| 214 | - "_" + prevId + "_" + prev.getManeuverType().ordinal(); |
|
| 215 | - bestPathEdges.add(edgeKey); |
|
| 216 | - } |
|
| 217 | - } |
|
| 218 | - prev = node; |
|
| 184 | + /** |
|
| 185 | + * Collects the best path edges based on the disambiguated best node selections. |
|
| 186 | + * An edge is marked as "best" if it connects two nodes where both endpoints |
|
| 187 | + * have their best (disambiguated) classification matching the edge's from/to types. |
|
| 188 | + */ |
|
| 189 | + private Set<String> collectBestPathEdges(MstManeuverGraphComponents graphComponents, |
|
| 190 | + Map<String, String> bestNodePerLevel, Map<MstGraphLevel, Integer> levelToId) { |
|
| 191 | + Set<String> bestPathEdges = new HashSet<>(); |
|
| 192 | + collectBestPathEdgesRecursive(graphComponents.getRoot(), bestPathEdges, bestNodePerLevel, levelToId); |
|
| 193 | + return bestPathEdges; |
|
| 194 | + } |
|
| 195 | + |
|
| 196 | + private void collectBestPathEdgesRecursive(MstGraphLevel parent, Set<String> bestPathEdges, |
|
| 197 | + Map<String, String> bestNodePerLevel, Map<MstGraphLevel, Integer> levelToId) { |
|
| 198 | + Integer parentId = levelToId.get(parent); |
|
| 199 | + String parentBestType = bestNodePerLevel.get(String.valueOf(parentId)); |
|
| 200 | + |
|
| 201 | + for (MstGraphLevel child : parent.getChildren()) { |
|
| 202 | + Integer childId = levelToId.get(child); |
|
| 203 | + String childBestType = bestNodePerLevel.get(String.valueOf(childId)); |
|
| 204 | + |
|
| 205 | + // Mark the edge between the best classifications as the best path edge |
|
| 206 | + if (parentBestType != null && childBestType != null) { |
|
| 207 | + int parentTypeOrdinal = ManeuverTypeForClassification.valueOf(parentBestType).ordinal(); |
|
| 208 | + int childTypeOrdinal = ManeuverTypeForClassification.valueOf(childBestType).ordinal(); |
|
| 209 | + String edgeKey = parentId + "_" + parentTypeOrdinal + "_" + childId + "_" + childTypeOrdinal; |
|
| 210 | + bestPathEdges.add(edgeKey); |
|
| 219 | 211 | } |
| 212 | + |
|
| 213 | + // Recurse to children |
|
| 214 | + collectBestPathEdgesRecursive(child, bestPathEdges, bestNodePerLevel, levelToId); |
|
| 220 | 215 | } |
| 221 | - return bestPathEdges; |
|
| 222 | 216 | } |
| 223 | 217 | |
| 224 | - private Map<String, String> collectBestNodePerLevel(MstManeuverGraphComponents graphComponents) { |
|
| 218 | + private Map<String, String> collectBestNodePerLevel(MstManeuverGraphComponents graphComponents, |
|
| 219 | + Map<MstGraphLevel, Integer> levelToId) { |
|
| 225 | 220 | final Map<String, String> bestNodePerLevel = new HashMap<>(); |
| 226 | - final Map<MstGraphLevel, Integer> levelToId = new HashMap<>(); |
|
| 227 | - final int[] nodeIdCounter = {0}; |
|
| 228 | - assignNodeIds(graphComponents.getRoot(), levelToId, nodeIdCounter); |
|
| 229 | 221 | // Use the MstBestPathsCalculatorImpl to get the best nodes |
| 230 | 222 | final MstBestPathsCalculatorImpl calculator = new MstBestPathsCalculatorImpl(transitionProbabilitiesCalculator); |
| 231 | 223 | for (final GraphLevelInference<MstGraphLevel> inference : calculator.getBestNodes(graphComponents)) { |