/// <summary> /// Find the chain of TaxiNodes that represent this runway /// </summary> /// <param name="taxiNodes"></param> /// <param name="taxiEdges"></param> /// <returns></returns> private List <TaxiNode> FindNodeChain(IEnumerable <TaxiNode> taxiNodes, IEnumerable <TaxiEdge> taxiEdges) { List <TaxiNode> nodes = new List <TaxiNode>(); // Start with the node nearest to the runway lat/lon TaxiNode currentNode = NearestNode; nodes.Add(currentNode); ulong previousNodeId = 0; do { // Now find an edge that is marked as 'runway' and that starts at the current node, but does not lead to the previous node IEnumerable <TaxiEdge> edgesToNext = taxiEdges.Where(e => e.IsRunway && (e.StartNode.Id == currentNode.Id && e.EndNode.Id != previousNodeId)); if (edgesToNext.Count() == 0) { break; } TaxiEdge edgeToNext = edgesToNext.First(); if (edgesToNext.Count() > 1) { double maxDeviation = double.MaxValue; foreach (TaxiEdge candidate in edgesToNext) { double deviation = VortexMath.TurnAngle(this.Bearing, VortexMath.BearingRadians(currentNode, candidate.EndNode)); if (deviation < maxDeviation) { edgeToNext = candidate; maxDeviation = deviation; } } } // Keep the current Id as the previous Id previousNodeId = currentNode.Id; // And get the new current node currentNode = taxiNodes.Single(n => n.Id == edgeToNext.EndNode.Id); if (currentNode != null) { nodes.Add(currentNode); } }while (currentNode != null); return(nodes); }
private void FindExits() { ExitGroups.Clear(); TaxiNode groupStartNode = null; // First, group all nodes foreach (TaxiNode node in RunwayNodes) { foreach (TaxiEdge edge in node.IncomingEdges) { if (edge.IsRunway) { continue; } if (edge.ReverseEdge == null) { continue; } TaxiEdge actualEdge = edge.ReverseEdge; double exitAngle = VortexMath.TurnAngle(actualEdge.Bearing, Bearing); if (Math.Abs(exitAngle) > VortexMath.Deg135Rad) { continue; } if (groupStartNode == null) { groupStartNode = node; ExitGroups.Add(node, new List <ExitPoint>()); } double landingLengthUsed = VortexMath.DistanceKM(DisplacedNode, node); // 'distance used' actually if (VortexMath.DistanceKM(groupStartNode, node) < 0.200) { ExitGroups[groupStartNode].Add(new ExitPoint() { OffRunwayNode = actualEdge.EndNode, OnRunwayNode = node, LandingLengthUsed = landingLengthUsed, TurnAngle = exitAngle }); } else { // add to new group groupStartNode = node; ExitGroups.Add(node, new List <ExitPoint>()); ExitGroups[groupStartNode].Add(new ExitPoint() { OffRunwayNode = actualEdge.EndNode, OnRunwayNode = node, LandingLengthUsed = landingLengthUsed, TurnAngle = exitAngle }); } } } if (ExitGroups.Count == 0) { return; // todo: add warning } // Then pick groups based upon distance List <ExitPoint> minimumExit = null; List <ExitPoint> mediumExit = null; List <ExitPoint> longExit = null; List <ExitPoint> maxExit = null; foreach (KeyValuePair <TaxiNode, List <ExitPoint> > exitGroup in ExitGroups.OrderBy(eg => eg.Value.First().LandingLengthUsed)) { if (minimumExit == null || minimumExit.First().LandingLengthUsed < VortexMath.Feet4000Km) { minimumExit = exitGroup.Value; } else if (mediumExit == null || mediumExit.First().LandingLengthUsed < VortexMath.Feet5000Km) { mediumExit = exitGroup.Value; } else if (longExit == null || longExit.First().LandingLengthUsed < VortexMath.Feet6500Km) { longExit = exitGroup.Value; } else { maxExit = exitGroup.Value; } } ExitGroups.Clear(); if (minimumExit != null) { ExitGroups.Add(minimumExit.First().OnRunwayNode, minimumExit); } if (mediumExit != null) { ExitGroups.Add(mediumExit.First().OnRunwayNode, mediumExit); } if (longExit != null) { ExitGroups.Add(longExit.First().OnRunwayNode, longExit); } if (maxExit != null) { ExitGroups.Add(maxExit.First().OnRunwayNode, maxExit); } foreach (var result in ExitGroups) { Logger.Log($"{Designator} Group: {result.Key.Id}"); ExitPoint right = result.Value.Where(ep => ep.TurnAngle > 0).OrderBy(ep => ep.TurnAngle).FirstOrDefault(); ExitPoint left = result.Value.Where(ep => ep.TurnAngle < 0).OrderByDescending(ep => ep.TurnAngle).FirstOrDefault(); ExitGroups[result.Key].Clear(); if (right != null) { Logger.Log($" Right Exit: {right.OnRunwayNode.Id}->{right.OffRunwayNode.Id} {right.TurnAngle * VortexMath.Rad2Deg:0.0} {right.LandingLengthUsed * VortexMath.KmToFoot:0}ft"); ExitGroups[result.Key].Add(right); } if (left != null) { Logger.Log($" Left Exit: {left.OnRunwayNode.Id}->{left.OffRunwayNode.Id} {left.TurnAngle * VortexMath.Rad2Deg:0.0} {left.LandingLengthUsed * VortexMath.KmToFoot:0}ft"); ExitGroups[result.Key].Add(left); } } }
private void FindEntries() { EntryGroups.Clear(); TaxiNode groupStartNode = null; bool foundGroups = false; // Nodes are ordered, longest remaining runway to runway end foreach (TaxiNode node in RunwayNodes) { double takeoffLengthRemaining = VortexMath.DistanceKM(node, OppositeEnd); if (takeoffLengthRemaining < VortexMath.Feet4000Km) { break; } foreach (TaxiEdge edge in node.IncomingEdges) { if (edge.IsRunway) { continue; } double entryAngle = VortexMath.TurnAngle(edge.Bearing, Bearing); if (Math.Abs(entryAngle) > VortexMath.Deg120Rad) { continue; } if (groupStartNode == null) { groupStartNode = node; EntryGroups.Add(node, new List <EntryPoint>()); } // Next can be simplified to 1 if (>= 0.250) / else with the actual add after the if else // for now this shows better what is going on if (VortexMath.DistanceKM(groupStartNode, node) < 0.200) { EntryGroups[groupStartNode].Add(new EntryPoint() { OffRunwayNode = edge.StartNode, OnRunwayNode = node, TakeoffLengthRemaining = takeoffLengthRemaining, TurnAngle = entryAngle }); } else if (EntryGroups.Count < 2) { // add to new group groupStartNode = node; EntryGroups.Add(node, new List <EntryPoint>()); EntryGroups[groupStartNode].Add(new EntryPoint() { OffRunwayNode = edge.StartNode, OnRunwayNode = node, TakeoffLengthRemaining = takeoffLengthRemaining, TurnAngle = entryAngle }); } else { foundGroups = true; break; } } if (foundGroups) { break; } } bool gotLeft = false; bool gotRight = false; bool useIntersections = Settings.UseIntersectionTakeOffs; double maxShiftKm = (double)Settings.MaxIntersectionShift * VortexMath.Foot2Km; // Find at least one enrty from the left and one from the right // If using intersections is allowed, add the intersections as option // as long as they are not too far from the runway start foreach (var result in EntryGroups) { Logger.Log($"{Designator} Group: {result.Key.Id}"); EntryPoint right = result.Value.Where(ep => ep.TurnAngle < 0).OrderByDescending(ep => ep.TurnAngle).FirstOrDefault(); EntryPoint left = result.Value.Where(ep => ep.TurnAngle > 0).OrderBy(ep => ep.TurnAngle).FirstOrDefault(); EntryGroups[result.Key].Clear(); if (right != null) { // No entry from the right, or using intersections is allowed if (!gotRight || useIntersections) { // No entry from the right, or current intersections is not too far from runway start if (!gotRight || (this.Length - maxShiftKm) < right.TakeoffLengthRemaining) { Logger.Log($" Right Entry: {right.OffRunwayNode.Id}->{right.OnRunwayNode.Id} {right.TurnAngle * VortexMath.Rad2Deg:0.0} {right.TakeoffLengthRemaining * VortexMath.KmToFoot:0}ft"); EntryGroups[result.Key].Add(right); gotRight = true; } } } if (left != null) { // No entry from the left, or using intersections is allowed if (!gotLeft || useIntersections) { // No entry from the left, or current intersections is not too far from runway start if (!gotLeft || (this.Length - maxShiftKm) < left.TakeoffLengthRemaining) { Logger.Log($" Left Entry: {left.OffRunwayNode.Id}->{left.OnRunwayNode.Id} {left.TurnAngle * VortexMath.Rad2Deg:0.0} {left.TakeoffLengthRemaining * VortexMath.KmToFoot:0}ft"); EntryGroups[result.Key].Add(left); gotLeft = true; } } } } }