Refactor ElkSharp hybrid routing and document speed path
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
internal static partial class ElkEdgeRouterHighway
|
||||
{
|
||||
private static Dictionary<string, List<int>> BuildTargetSideGroups(
|
||||
IReadOnlyList<ElkRoutedEdge> edges,
|
||||
IReadOnlyDictionary<string, ElkPositionedNode> nodesById,
|
||||
double graphMinY,
|
||||
double graphMaxY)
|
||||
{
|
||||
var edgesByTargetSide = new Dictionary<string, List<int>>(StringComparer.Ordinal);
|
||||
for (var i = 0; i < edges.Count; i++)
|
||||
{
|
||||
var edge = edges[i];
|
||||
if (!ShouldProcessEdge(edge, graphMinY, graphMaxY))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var targetNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ElkShapeBoundaries.IsGatewayShape(targetNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var path = ExtractFullPath(edge);
|
||||
if (path.Count < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var side = ElkEdgeRoutingGeometry.ResolveBoundarySide(path[^1], targetNode);
|
||||
var key = $"{targetNode.Id}|{side}";
|
||||
if (!edgesByTargetSide.TryGetValue(key, out var list))
|
||||
{
|
||||
list = [];
|
||||
edgesByTargetSide[key] = list;
|
||||
}
|
||||
|
||||
list.Add(i);
|
||||
}
|
||||
|
||||
return edgesByTargetSide;
|
||||
}
|
||||
|
||||
private static GroupEvaluation? EvaluateTargetSideGroup(
|
||||
IReadOnlyList<ElkRoutedEdge> edges,
|
||||
IReadOnlyList<int> edgeIndices,
|
||||
ElkPositionedNode targetNode,
|
||||
string side,
|
||||
double minLineClearance)
|
||||
{
|
||||
var members = edgeIndices
|
||||
.Select(index => CreateMember(edges[index], index, side))
|
||||
.Where(member => member.Path.Count >= 2)
|
||||
.OrderBy(member => member.EdgeId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
if (members.Count < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var pairMetrics = ComputePairMetrics(members);
|
||||
var actualGap = ComputeMinEndpointGap(members);
|
||||
var requiredGap = ElkBoundarySlots.ResolveRequiredBoundarySlotGap(
|
||||
targetNode,
|
||||
side,
|
||||
members.Count,
|
||||
minLineClearance);
|
||||
var requiresSpread = (actualGap + CoordinateTolerance) < requiredGap
|
||||
&& !pairMetrics.AllPairsApplicable;
|
||||
if (!requiresSpread && pairMetrics.ShortestSharedRatio < MinHighwayRatio)
|
||||
{
|
||||
requiresSpread = pairMetrics.HasSharedSegment;
|
||||
}
|
||||
|
||||
var diagnostic = new ElkHighwayDiagnostics
|
||||
{
|
||||
TargetNodeId = targetNode.Id,
|
||||
SharedAxis = side,
|
||||
SharedCoord = Math.Round(members.Average(member => member.EndpointCoord), 1),
|
||||
EdgeIds = members.Select(member => member.EdgeId).ToArray(),
|
||||
MinRatio = pairMetrics.HasSharedSegment
|
||||
? Math.Round(pairMetrics.ShortestSharedRatio, 3)
|
||||
: 0d,
|
||||
WasBroken = requiresSpread,
|
||||
Reason = requiresSpread
|
||||
? pairMetrics.HasSharedSegment && pairMetrics.ShortestSharedRatio < MinHighwayRatio
|
||||
? $"shared ratio {pairMetrics.ShortestSharedRatio:F2} < {MinHighwayRatio:F2}"
|
||||
: $"gap {actualGap:F0}px < required gap {requiredGap:F0}px"
|
||||
: pairMetrics.AllPairsApplicable
|
||||
? $"shared ratio {pairMetrics.ShortestSharedRatio:F2} >= {MinHighwayRatio:F2}"
|
||||
: $"gap {actualGap:F0}px >= required gap {requiredGap:F0}px",
|
||||
};
|
||||
|
||||
return new GroupEvaluation(members, diagnostic);
|
||||
}
|
||||
|
||||
private static HighwayPairMetrics ComputePairMetrics(IReadOnlyList<HighwayMember> members)
|
||||
{
|
||||
var shortestSharedRatio = double.MaxValue;
|
||||
var hasSharedSegment = false;
|
||||
var allPairsApplicable = true;
|
||||
|
||||
for (var i = 0; i < members.Count; i++)
|
||||
{
|
||||
for (var j = i + 1; j < members.Count; j++)
|
||||
{
|
||||
var sharedLength = ElkEdgeRoutingGeometry.ComputeLongestSharedApproachSegmentLength(
|
||||
members[i].Path,
|
||||
members[j].Path);
|
||||
if (sharedLength <= 1d)
|
||||
{
|
||||
allPairsApplicable = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
hasSharedSegment = true;
|
||||
var shortestPath = Math.Min(members[i].PathLength, members[j].PathLength);
|
||||
if (shortestPath <= 1d)
|
||||
{
|
||||
allPairsApplicable = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var ratio = sharedLength / shortestPath;
|
||||
shortestSharedRatio = Math.Min(shortestSharedRatio, ratio);
|
||||
if (ratio < MinHighwayRatio)
|
||||
{
|
||||
allPairsApplicable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new HighwayPairMetrics(
|
||||
HasSharedSegment: hasSharedSegment,
|
||||
AllPairsApplicable: allPairsApplicable && hasSharedSegment,
|
||||
ShortestSharedRatio: hasSharedSegment ? shortestSharedRatio : 0d);
|
||||
}
|
||||
|
||||
private static double ComputeMinEndpointGap(IReadOnlyList<HighwayMember> members)
|
||||
{
|
||||
var coords = members
|
||||
.Select(member => member.EndpointCoord)
|
||||
.OrderBy(value => value)
|
||||
.ToArray();
|
||||
if (coords.Length < 2)
|
||||
{
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
var minGap = double.MaxValue;
|
||||
for (var i = 1; i < coords.Length; i++)
|
||||
{
|
||||
minGap = Math.Min(minGap, coords[i] - coords[i - 1]);
|
||||
}
|
||||
|
||||
return minGap;
|
||||
}
|
||||
|
||||
private static HighwayMember CreateMember(ElkRoutedEdge edge, int index, string side)
|
||||
{
|
||||
var path = ExtractFullPath(edge);
|
||||
var endpointCoord = side is "left" or "right"
|
||||
? path[^1].Y
|
||||
: path[^1].X;
|
||||
return new HighwayMember(
|
||||
Index: index,
|
||||
EdgeId: edge.Id,
|
||||
Path: path,
|
||||
PathLength: ElkEdgeRoutingGeometry.ComputePathLength(edge),
|
||||
EndpointCoord: endpointCoord);
|
||||
}
|
||||
|
||||
private readonly record struct HighwayMember(
|
||||
int Index,
|
||||
string EdgeId,
|
||||
List<ElkPoint> Path,
|
||||
double PathLength,
|
||||
double EndpointCoord);
|
||||
|
||||
private readonly record struct HighwayPairMetrics(
|
||||
bool HasSharedSegment,
|
||||
bool AllPairsApplicable,
|
||||
double ShortestSharedRatio);
|
||||
|
||||
private readonly record struct GroupEvaluation(
|
||||
IReadOnlyList<HighwayMember> Members,
|
||||
ElkHighwayDiagnostics Diagnostic);
|
||||
}
|
||||
Reference in New Issue
Block a user