/// <summary> /// Extract the route that starts at TaxiNode 'startNode' /// </summary> /// <param name="edges">A list of all available edges</param> /// <param name="startNode">The first node of the route</param> /// <param name="size">The maximum size for which this route is valid</param> /// <returns>The route as a linked list of nodes with additional informationthat will be needed when writing the route to a file</returns> public static ResultRoute ExtractRoute(IEnumerable <TaxiEdge> edges, TaxiNode startNode, XPlaneAircraftCategory size) { ResultRoute extracted = new ResultRoute(size); extracted.Runway = null; extracted.StartNode = startNode; ulong node1 = extracted.StartNode.Id; extracted.Distance = startNode.DistanceToTarget; TaxiNode pathNode; pathNode = startNode.NextNodeToTarget; TaxiEdge sneakEdge = null; if (pathNode != null) { sneakEdge = edges.SingleOrDefault(e => e.StartNode.Id == node1 && e.EndNode.Id == pathNode.Id); } // Set up the first link extracted.RouteStart = new LinkedNode() { Node = startNode.NextNodeToTarget, Next = null, Edge = sneakEdge }; LinkedNode currentLink = extracted.RouteStart; // And follow the path... while (pathNode != null) { double currentBearing = currentLink.Node.BearingToTarget; ulong node2 = pathNode.Id; TaxiEdge edge = edges.Single(e => e.StartNode.Id == node1 && e.EndNode.Id == node2); if (pathNode.NextNodeToTarget != null && pathNode.NextNodeToTarget.DistanceToTarget > 0) { double nextBearing = pathNode.NextNodeToTarget.BearingToTarget; double turn = VortexMath.AbsTurnAngle(currentBearing, nextBearing); // This filters out very sharp turns if an alternate exists in exchange for a longer route: // todo: parameters. Now => if more than 120 degrees and alternate < 45 exists use alternate if (turn > VortexMath.Deg120Rad) { IEnumerable <TaxiEdge> altEdges = edges.Where(e => e.StartNode.Id == pathNode.NextNodeToTarget.Id && e.EndNode.Id != pathNode.NextNodeToTarget.NextNodeToTarget.Id && e.EndNode.Id != pathNode.Id); foreach (TaxiEdge te in altEdges) { if (te.EndNode.DistanceToTarget < double.MaxValue) { double newTurn = VortexMath.AbsTurnAngle(currentBearing, te.EndNode.BearingToTarget); if (newTurn < VortexMath.Deg100Rad) { // Fiddling with Dijkstra results like this may generate a loop in the route // So scan it before actually using the reroute if (!hasLoop(te.EndNode, pathNode)) { pathNode.NextNodeToTarget.OverrideToTarget = te.EndNode; break; } } } } } else if (turn > VortexMath.Deg005Rad) // Any turn larger than 5 degrees: if going straight does not lead to more than 250m extra distance... go straight. { IEnumerable <TaxiEdge> altEdges = edges.Where(e => e.StartNode.Id == pathNode.NextNodeToTarget.Id && e.EndNode.Id != pathNode.NextNodeToTarget.NextNodeToTarget.Id && e.EndNode.Id != pathNode.Id); foreach (TaxiEdge te in altEdges) { if (te.EndNode.DistanceToTarget < (pathNode.NextNodeToTarget.NextNodeToTarget.DistanceToTarget + 0.250)) { double newTurn = VortexMath.AbsTurnAngle(currentBearing, te.EndNode.BearingToTarget); if (newTurn < VortexMath.Deg005Rad) { // Fiddling with Dijkstra results like this may generate a loop in the route // So scan it before actually using the reroute if (!hasLoop(te.EndNode, pathNode)) { pathNode.NextNodeToTarget.OverrideToTarget = te.EndNode; break; } } } } } } TaxiNode nextNode = (pathNode.OverrideToTarget != null) ? pathNode.OverrideToTarget : pathNode.NextNodeToTarget; currentLink.Next = new LinkedNode() { Node = nextNode, Next = null, }; node1 = node2; currentLink.Edge = edge; currentLink = currentLink.Next; extracted.TargetNode = pathNode; pathNode.OverrideToTarget = null; pathNode = nextNode; } return(extracted); }
public static void Smooth(List <SteerPoint> steerPoints) { // todo: Could add a pass to merge points within 25 meters or so. // Take (linear) middle between points, average speed, set 'maximum' runway ops on merged point // Maybe after smoothing works best for (int i = 1; i < steerPoints.Count() - 1; i++) { if (steerPoints[i] is PushbackPoint) { continue; } SteerPoint previous = steerPoints[i - 1]; SteerPoint current = steerPoints[i]; SteerPoint next = steerPoints[i + 1]; double incomingBearing = VortexMath.BearingRadians(previous, current); double outgoingBearing = VortexMath.BearingRadians(current, next); double turnAngle = VortexMath.AbsTurnAngle(incomingBearing, outgoingBearing); if (!current.Protected && turnAngle < 2.5 * VortexMath.Deg2Rad && !(current is RunwayPoint)) { if (previous.Name == next.Name && previous.Speed == next.Speed) { steerPoints.RemoveAt(i); i--; } } else if (!current.Protected && turnAngle > VortexMath.PI025) // 45 degrees { double smoothingDistance = 0.050 * (turnAngle / VortexMath.PI); // 90 degrees = 0.5 PI / PI = 0.5 * 0.05 km = 25 meters double currentLatitude = current.Latitude; double currentLongitude = current.Longitude; if (VortexMath.DistanceKM(previous.Latitude, previous.Longitude, currentLatitude, currentLongitude) > smoothingDistance) { // Shift the current point a bit back VortexMath.PointFrom(currentLatitude, currentLongitude, incomingBearing + VortexMath.PI, smoothingDistance, ref current.Latitude, ref current.Longitude); if (current.Speed > 14) { current.Speed /= 2; } } else { // skip the current steerPoints.RemoveAt(i); i--; } double distanceToNext = VortexMath.DistanceKM(currentLatitude, currentLongitude, next.Latitude, next.Longitude); if (distanceToNext > smoothingDistance) { // Insert an extra point SteerPoint newPoint = next.Duplicate(); VortexMath.PointFrom(currentLatitude, currentLongitude, outgoingBearing, smoothingDistance, ref newPoint.Latitude, ref newPoint.Longitude); // If room for additional speed up point, reduce acceleration on this point if (distanceToNext > 2.5 * smoothingDistance) { newPoint.Speed = (newPoint.Speed * 2) / 3; } steerPoints.Insert(i + 1, newPoint); i++; } if (distanceToNext > 2.5 * smoothingDistance) { // Insert an extra point SteerPoint newPoint = next.Duplicate(); VortexMath.PointFrom(currentLatitude, currentLongitude, outgoingBearing, 2.5 * smoothingDistance, ref newPoint.Latitude, ref newPoint.Longitude); steerPoints.Insert(i + 1, newPoint); i++; } } } }
private IEnumerable <SteerPoint> BuildSteerPoints(ResultRoute route, TaxiNode runwayExitNode) { List <SteerPoint> steerPoints = new List <SteerPoint>(); // Route should start at the (displaced) threshold RunwayPoint threshold = new RunwayPoint(route.Runway.DisplacedNode, 55, $"{route.Runway.Designator} Threshold", route.RouteStart.Edge.ActiveForRunway(route.Runway.Designator)) { OnRunway = true, IsExiting = true }; steerPoints.Add(threshold); foreach (TaxiNode node in route.Runway.RunwayNodes) { int speed = (node == runwayExitNode) ? 35 : 55; steerPoints.Add(new RunwayPoint(node.Latitude, node.Longitude, speed, $"{route.Runway.Designator}", route.RouteStart.Edge.ActiveForRunway(route.Runway.Designator))); if (node == runwayExitNode) // Key of the dictionary is the last node on the runway centerline for this route { break; } } // This is the first node off the runway centerline steerPoints.Add(new RunwayPoint(route.StartNode, 30, route.RouteStart.Edge.LinkName, route.RouteStart.Edge.ActiveForRunway(route.Runway.Designator))); LinkedNode link = route.RouteStart; while (link.Node != null) { bool activeZone = false; string activeFor = ""; if (link.Edge.ActiveZone) { activeZone = true; activeFor = link.Edge.ActiveForRunway(""); } else if (link.Next.Edge != null && link.Next.Edge.ActiveZone) { activeZone = true; activeFor = link.Next.Edge.ActiveForRunway(""); } if (activeZone) { steerPoints.Add(new RunwayPoint(link.Node.Latitude, link.Node.Longitude, 15, $"{link.Edge.LinkName}", activeFor)); } else { steerPoints.Add(new SteerPoint(link.Node.Latitude, link.Node.Longitude, 15, $"{link.Edge.LinkName}")); } link = link.Next; } // remove last point if it takes us past the 'pushback point' if (steerPoints.Count > 1) { SteerPoint oneButLast = steerPoints.ElementAt(steerPoints.Count - 2); SteerPoint last = steerPoints.ElementAt(steerPoints.Count - 1); double lastBearing = VortexMath.BearingRadians(oneButLast, last); double bearingToPush = VortexMath.BearingRadians(last.Latitude, last.Longitude, Parking.PushBackLatitude, Parking.PushBackLongitude); double turnToPush = VortexMath.AbsTurnAngle(lastBearing, bearingToPush); if (turnToPush > VortexMath.Deg100Rad) { steerPoints.RemoveAt(steerPoints.Count - 1); } } // todo: how does this all work with freaky pushback points? // todo: tie downs steerPoints.Add(new SteerPoint(Parking.PushBackLatitude, Parking.PushBackLongitude, 5, Parking.Name)); steerPoints.Add(new ParkingPoint(Parking.Latitude, Parking.Longitude, 5, Parking.Name, Parking.Bearing, true)); //RouteProcessor.Smooth(steerPoints); RouteProcessor.ProcessRunwayOperations(steerPoints); if (MaxInPoints < steerPoints.Count) { MaxInPoints = steerPoints.Count; } return(steerPoints); }