using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private static string[] ExpandRepeatCollectorRepairSet( IReadOnlyCollection selectedEdgeIds, IReadOnlyCollection edges, IReadOnlyCollection nodes) { var selected = selectedEdgeIds.ToHashSet(StringComparer.Ordinal); foreach (var group in ElkRepeatCollectorCorridors.DetectSharedLaneGroups(edges, nodes)) { if (!group.EdgeIds.Any(selected.Contains)) { continue; } foreach (var edgeId in group.EdgeIds) { selected.Add(edgeId); } } return selected .OrderBy(edgeId => edgeId, StringComparer.Ordinal) .ToArray(); } private static string[] ExpandTargetApproachJoinRepairSet( IReadOnlyCollection selectedEdgeIds, IReadOnlyCollection edges, IReadOnlyCollection nodes, double minLineClearance) { var selected = selectedEdgeIds.ToHashSet(StringComparer.Ordinal); var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal); var edgeArray = edges.ToArray(); foreach (var group in edgeArray.GroupBy(edge => edge.TargetNodeId ?? string.Empty, StringComparer.Ordinal)) { if (string.IsNullOrWhiteSpace(group.Key) || !nodesById.TryGetValue(group.Key, out var targetNode)) { continue; } var targetEdges = group.ToArray(); for (var i = 0; i < targetEdges.Length; i++) { var leftEdge = targetEdges[i]; var leftPath = ExtractPath(leftEdge); if (leftPath.Count < 2) { continue; } var leftSide = ElkEdgeRoutingGeometry.ResolveBoundaryApproachSide(leftPath[^1], leftPath[^2], targetNode); for (var j = i + 1; j < targetEdges.Length; j++) { var rightEdge = targetEdges[j]; var rightPath = ExtractPath(rightEdge); if (rightPath.Count < 2) { continue; } var rightSide = ElkEdgeRoutingGeometry.ResolveBoundaryApproachSide(rightPath[^1], rightPath[^2], targetNode); if (!string.Equals(leftSide, rightSide, StringComparison.Ordinal)) { continue; } if (!HasTargetApproachJoinPair(leftPath, rightPath, minLineClearance)) { continue; } selected.Add(leftEdge.Id); selected.Add(rightEdge.Id); } } } return selected .OrderBy(edgeId => edgeId, StringComparer.Ordinal) .ToArray(); } private static string[] ExpandSharedLaneRepairSet( IReadOnlyCollection selectedEdgeIds, IReadOnlyCollection edges, IReadOnlyCollection nodes) { var selected = selectedEdgeIds.ToHashSet(StringComparer.Ordinal); foreach (var (leftEdgeId, rightEdgeId) in ElkEdgeRoutingScoring.DetectSharedLaneConflicts(edges, nodes)) { if (!selected.Contains(leftEdgeId) && !selected.Contains(rightEdgeId)) { continue; } selected.Add(leftEdgeId); selected.Add(rightEdgeId); } return selected .OrderBy(edgeId => edgeId, StringComparer.Ordinal) .ToArray(); } private static string[] ExpandUnderNodeRepairSet( IReadOnlyCollection selectedEdgeIds, IReadOnlyCollection edges, IReadOnlyCollection nodes) { var selected = selectedEdgeIds.ToHashSet(StringComparer.Ordinal); foreach (var edge in edges) { if (ElkEdgeRoutingScoring.CountUnderNodeViolations([edge], nodes) > 0) { selected.Add(edge.Id); } } return selected .OrderBy(edgeId => edgeId, StringComparer.Ordinal) .ToArray(); } private static List ExtractPath(ElkRoutedEdge edge) { var path = new List(); foreach (var section in edge.Sections) { if (path.Count == 0) { path.Add(section.StartPoint); } path.AddRange(section.BendPoints); path.Add(section.EndPoint); } return path; } private static bool HasTargetApproachJoinPair( IReadOnlyList leftPath, IReadOnlyList rightPath, double minLineClearance, int maxSegmentsFromEnd = 3) { var leftSegments = FlattenSegmentsNearEnd(leftPath, maxSegmentsFromEnd); var rightSegments = FlattenSegmentsNearEnd(rightPath, maxSegmentsFromEnd); foreach (var leftSegment in leftSegments) { foreach (var rightSegment in rightSegments) { if (!ElkEdgeRoutingGeometry.AreParallelAndClose( leftSegment.Start, leftSegment.End, rightSegment.Start, rightSegment.End, minLineClearance)) { continue; } var overlap = ElkEdgeRoutingGeometry.ComputeSharedSegmentLength( leftSegment.Start, leftSegment.End, rightSegment.Start, rightSegment.End); if (overlap > 8d) { return true; } var leftLength = ElkEdgeRoutingGeometry.ComputeSegmentLength(leftSegment.Start, leftSegment.End); var rightLength = ElkEdgeRoutingGeometry.ComputeSegmentLength(rightSegment.Start, rightSegment.End); if (Math.Min(leftLength, rightLength) > 8d) { return true; } } } return false; } private static IReadOnlyList FlattenSegmentsNearEnd( IReadOnlyList path, int maxSegmentsFromEnd) { if (path.Count < 2 || maxSegmentsFromEnd <= 0) { return []; } var startIndex = Math.Max(0, path.Count - (maxSegmentsFromEnd + 1)); var segments = new List(); for (var i = startIndex; i < path.Count - 1; i++) { segments.Add(new RoutedEdgeSegment(string.Empty, path[i], path[i + 1])); } return segments; } }