diff --git a/Source/Libraries/openXDA.Model/TransmissionElements/Line.cs b/Source/Libraries/openXDA.Model/TransmissionElements/Line.cs index 404a9fbd94..92dfcbb937 100644 --- a/Source/Libraries/openXDA.Model/TransmissionElements/Line.cs +++ b/Source/Libraries/openXDA.Model/TransmissionElements/Line.cs @@ -129,9 +129,13 @@ private List QuerySegments() private List QueryPath() { - - //Start by finding all the ends - List lineEnds = Segments.Where(item => item.IsEnd).ToList(); + // Start by finding all the ends + bool IsEnd(LineSegment segment) => + segment.IsEnd || + segment.ConnectedSegments.Count <= 1 || + IsForkedEnd(segment); + + List lineEnds = [.. Segments.Where(IsEnd)]; if (lineEnds.Count == 1) return new List() @@ -189,40 +193,62 @@ private List QueryPath() } /// - /// Attempts to walk the Line from start to end. + /// Determines if all pairs of segments connected to the given segment are connected to each other. + /// + private bool IsForkedEnd(LineSegment segment) + { + bool AreDirectConnected(LineSegment s1, LineSegment s2) => s1.ConnectedSegments + .Any(connection => connection.ParentSegment == s2.ID || connection.ChildSegment == s2.ID); + + List nextSegments = [.. GetNextSegments(segment)]; + + return nextSegments + .SelectMany(_ => nextSegments, (S1, S2) => (S1, S2)) + .Where(tuple => tuple.S1.ID < tuple.S2.ID) + .All(tuple => AreDirectConnected(tuple.S1, tuple.S2)); + } + + /// + /// Attempts to walk the line from start to end. /// - /// - /// - /// private List WalkTheLine(LineSegment start, LineSegment end, Stack stack = null) { - if (stack is null) - stack = new Stack(); + stack ??= new Stack([start.ID]); + + List nextSegments = [.. GetNextSegments(start) + .Where(next => !stack.Contains(next.ID))]; - foreach (LineSegment segment in GetNextSegement(start)) + // Don't treat taps as bridges; if start, F1, and F2 are all connected + // to each other, do not allow the F1 path to visit F2 or vice-versa + foreach (LineSegment next in nextSegments) + stack.Push(next.ID); + + try { - if (segment.ID == end.ID) - { - // we found the end, so we can return the path - return new List() { start, segment }; - } - if (stack.Contains(segment.ID)) + foreach (LineSegment next in nextSegments) { - if (stack.Peek() != segment.ID) - Log.Error($"Line {this.AssetKey} has a looped line segment ({segment.AssetKey}). This is causing issues in fault distance computations."); - continue; + if (next.ID == end.ID) + return [start, next]; } - stack.Push(start.ID); - List follow = WalkTheLine(segment, end, stack); - stack.Pop(); - if (follow.Count > 0) + + foreach (LineSegment next in nextSegments) { + List follow = WalkTheLine(next, end, stack); + + if (follow.Count == 0) + continue; + follow.Insert(0, start); return follow; } - } - return new List(); + return []; + } + finally + { + for (int i = 0; i < nextSegments.Count; i++) + stack.Pop(); + } } /// @@ -230,9 +256,9 @@ private List WalkTheLine(LineSegment start, LineSegment end, Stack< /// /// /// - private IEnumerable GetNextSegement(LineSegment current) + private IEnumerable GetNextSegments(LineSegment current) { - return current.connectedSegments.Select(item => + return current.ConnectedSegments.Select(item => { if (item.ChildSegment == current.ID) return item.Parent; diff --git a/Source/Libraries/openXDA.Model/TransmissionElements/LineSegment.cs b/Source/Libraries/openXDA.Model/TransmissionElements/LineSegment.cs index dd7326d09c..2a0b22e339 100644 --- a/Source/Libraries/openXDA.Model/TransmissionElements/LineSegment.cs +++ b/Source/Libraries/openXDA.Model/TransmissionElements/LineSegment.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using GSF.Data; @@ -71,7 +72,7 @@ public Line Line [JsonIgnore] [NonRecordField] - public List connectedSegments + public List ConnectedSegments { get => m_connectedSegements ?? (m_connectedSegements = QueryConnectedSegements()); set => m_connectedSegements = value; @@ -173,5 +174,19 @@ private List QueryConnectedSegements() } #endregion + + #region [ Obsolete ] + + [JsonIgnore] + [NonRecordField] + [Obsolete("Deprecated due to incorrect casing; use ConnectedSegments instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public List connectedSegments + { + get => ConnectedSegments; + set => ConnectedSegments = value; + } + + #endregion } }