/// <summary> /// Add a resulting route for a specific runway exit and size /// </summary> /// <param name="maxSizeCurrentResult">The maximum size allowed on the current route</param> /// <param name="runwayExitNode">The runway node for this exit</param> /// <param name="pathStartNode">The frist node 'departing' the runway</param> /// <param name="r">The runway it self</param> public void AddResult(XPlaneAircraftCategory maxSizeCurrentResult, TaxiNode runwayExitNode, TaxiNode pathStartNode, Runway r, double availableRunwayLength) { if (!_results.ContainsKey(runwayExitNode)) { _results[runwayExitNode] = new Dictionary <XPlaneAircraftCategory, ResultRoute>(); } Dictionary <XPlaneAircraftCategory, ResultRoute> originResults = _results[runwayExitNode]; // If no results yet for this node, just add the current route if (originResults.Count == 0) { originResults.Add(maxSizeCurrentResult, ResultRoute.ExtractRoute(_edges, pathStartNode, maxSizeCurrentResult)); originResults[maxSizeCurrentResult].Runway = r; originResults[maxSizeCurrentResult].AvailableRunwayLength = availableRunwayLength; } else { XPlaneAircraftCategory minSize = originResults.Min(or => or.Key); if (originResults[minSize].Distance > pathStartNode.DistanceToTarget) { if (minSize > maxSizeCurrentResult) { originResults.Add(maxSizeCurrentResult, ResultRoute.ExtractRoute(_edges, pathStartNode, maxSizeCurrentResult)); originResults[maxSizeCurrentResult].Runway = r; originResults[maxSizeCurrentResult].AvailableRunwayLength = availableRunwayLength; originResults[minSize].MinSize = (maxSizeCurrentResult + 1); } else if (minSize == maxSizeCurrentResult) { originResults[minSize] = ResultRoute.ExtractRoute(_edges, pathStartNode, maxSizeCurrentResult); originResults[minSize].Runway = r; originResults[minSize].AvailableRunwayLength = availableRunwayLength; } } } }
public int WriteRoutes(string outputPath, bool kml) { int count = 0; foreach (KeyValuePair <TaxiNode, Dictionary <XPlaneAircraftCategory, ResultRoute> > sizeRoutes in _results) { for (XPlaneAircraftCategory size = XPlaneAircraftCategory.Max - 1; size >= XPlaneAircraftCategory.A; size--) { if (sizeRoutes.Value.ContainsKey(size)) { ResultRoute route = sizeRoutes.Value[size]; if (route.TargetNode == null) { continue; } if (route.AvailableRunwayLength < VortexMath.Feet3000Km) { continue; } foreach (Parking currentParking in route.Parkings) { IEnumerable <WorldTrafficAircraftType> wtTypes = AircraftTypeConverter.WTTypesFromXPlaneLimits(XPlaneAircraftCategory.A, route.MaxSize, currentParking.Operation); if (route.AvailableRunwayLength < VortexMath.Feet9000Km) { WorldTrafficAircraftType[] big = { WorldTrafficAircraftType.SuperHeavy, WorldTrafficAircraftType.HeavyJet }; wtTypes = wtTypes.Except(big); } if (route.AvailableRunwayLength < VortexMath.Feet6500Km) { WorldTrafficAircraftType[] big = { WorldTrafficAircraftType.LargeJet }; wtTypes = wtTypes.Except(big); } if (route.AvailableRunwayLength < VortexMath.Feet5000Km) { WorldTrafficAircraftType[] big = { WorldTrafficAircraftType.MediumJet, WorldTrafficAircraftType.LightJet }; wtTypes = wtTypes.Except(big); } if (route.AvailableRunwayLength < VortexMath.Feet4000Km) { WorldTrafficAircraftType[] big = { WorldTrafficAircraftType.LargeProp, WorldTrafficAircraftType.MediumProp }; wtTypes = wtTypes.Except(big); } if (wtTypes.Count() == 0) { continue; } IEnumerable <SteerPoint> steerPoints = BuildSteerPoints(currentParking, route); if (steerPoints.Count() <= Settings.MaxSteerpoints) { string allSizes = string.Join(" ", wtTypes.Select(w => (int)w).OrderBy(w => w)); string sizeName = (wtTypes.Count() == 10) ? "all" : allSizes.Replace(" ", ""); string fileName = Path.Combine(outputPath, $"{currentParking.FileNameSafeName}_to_{Runway.Designator}-{route.AvailableRunwayLength * VortexMath.KmToFoot:00000}_{sizeName}"); int military = (currentParking.Operation == OperationType.Military) ? 1 : 0; int cargo = (currentParking.Operation == OperationType.Cargo) ? 1 : 0; using (RouteWriter sw = RouteWriter.Create(kml ? 0 : 1, fileName, allSizes, cargo, military, Runway.Designator, "NOSEWHEEL")) { count++; foreach (SteerPoint steerPoint in steerPoints) { sw.Write(steerPoint); } } } else { Logger.Log($"Route from <{currentParking.FileNameSafeName}> to {Runway.Designator} not written. Too many steerpoints ({steerPoints.Count()} vs {Settings.MaxSteerpoints})"); } } } } } return(count); }
private IEnumerable <SteerPoint> BuildSteerPoints(Parking currentParking, ResultRoute route) { LinkedNode link = route.RouteStart; TaxiNode nodeToWrite = route.StartNode; EntryPoint entryPoint = route.RunwayEntryPoint; List <SteerPoint> steerPoints = new List <SteerPoint> { new ParkingPoint(currentParking.Latitude, currentParking.Longitude, 3, $"{currentParking.Name}", currentParking.Bearing, false) }; // Write Pushback node, allowing room for turn double addLat = 0; double addLon = 0; // See if we need to skip the first route node if (currentParking.AlternateAfterPushBack != null && currentParking.AlternateAfterPushBack == route.RouteStart.Node) { // Our pushback point is better than the first point of the route nodeToWrite = currentParking.AlternateAfterPushBack; } // insert one more point here where the plane is pushed a little bit away from the next point if (currentParking.LocationType == StartUpLocationType.Gate) { if (nodeToWrite != null) { double nextPushBearing; if (VortexMath.DistanceKM(nodeToWrite.Latitude, nodeToWrite.Longitude, currentParking.PushBackLatitude, currentParking.PushBackLongitude) > 0.010) { // Push target is a virtual node nextPushBearing = VortexMath.BearingRadians(nodeToWrite.Latitude, nodeToWrite.Longitude, currentParking.PushBackLatitude, currentParking.PushBackLongitude); } else { // Push target is very close to the actual first node of the route nextPushBearing = (nodeToWrite.BearingToTarget + VortexMath.PI) % VortexMath.PI2; } double turn = VortexMath.TurnAngle(currentParking.Bearing + VortexMath.PI, nextPushBearing); double turnAbs = Math.Abs(turn); double factor = ((turnAbs) / VortexMath.PI); // 0...0.5.....1 factor = (factor * factor) + factor / 4; // 0...0.375...1.25 double distance = 0.040 * factor; // 0m...15m ...50m if (turnAbs < VortexMath.Deg135Rad) { // Try to trun the aircraft to the bearing it will need to go in after pushback // First point is on the pushback heading, but away from the actual target to allow the AC to turn VortexMath.PointFrom(currentParking.PushBackLatitude, currentParking.PushBackLongitude, currentParking.Bearing, distance, ref addLat, ref addLon); steerPoints.Add(new PushbackPoint(addLat, addLon, 2, $"{currentParking.Name}")); // Second point is on the (extended) line of the first link of the actual route VortexMath.PointFrom(currentParking.PushBackLatitude, currentParking.PushBackLongitude, nextPushBearing, distance, ref addLat, ref addLon); steerPoints.Add(new PushbackPoint(addLat, addLon, 2, $"{link.Edge.LinkName}")); // Third point is on the same line but a little bit extra backwards to get the nose in the intended heading VortexMath.PointFrom(currentParking.PushBackLatitude, currentParking.PushBackLongitude, nextPushBearing, distance + 0.015, ref addLat, ref addLon); steerPoints.Add(new SteerPoint(addLat, addLon, 8, $"{link.Edge.LinkName}", true)); } else { // Let's just turn it to a 90 degree angle with the first edge // First point is on the pushback heading, but away from the actual target to allow the AC to turn VortexMath.PointFrom(currentParking.PushBackLatitude, currentParking.PushBackLongitude, currentParking.Bearing, distance, ref addLat, ref addLon); steerPoints.Add(new PushbackPoint(addLat, addLon, 2, $"{currentParking.Name}")); // Second point is on the (extended) line of the first link of the actual route, but much closer then for the full turn VortexMath.PointFrom(currentParking.PushBackLatitude, currentParking.PushBackLongitude, nextPushBearing, distance / 2.0, ref addLat, ref addLon); steerPoints.Add(new PushbackPoint(addLat, addLon, 2, $"{link.Edge.LinkName}")); // Third point is on +/-90 degree angle from the first link VortexMath.PointFrom(addLat, addLon, (turn > 0) ? nextPushBearing + VortexMath.PI05 : nextPushBearing - VortexMath.PI05, 0.015, ref addLat, ref addLon); steerPoints.Add(new SteerPoint(addLat, addLon, 5, $"{link.Edge.LinkName}", true)); // Add a fourth point back on the intended line steerPoints.Add(new SteerPoint(currentParking.PushBackLatitude, currentParking.PushBackLongitude, 8, $"{link.Edge.LinkName}")); } } } else { // Tie down, hangar, misc: just add the 'pushback' point as first target, smoothing should take care of the rest steerPoints.Add(new SteerPoint(currentParking.PushBackLatitude, currentParking.PushBackLongitude, 8, $"{link.Edge.LinkName}")); } if (nodeToWrite != link.Node) { steerPoints.Add(new SteerPoint(nodeToWrite.Latitude, nodeToWrite.Longitude, 8, $"{link.Edge.LinkName}")); } while (link.Node != null) { bool activeZone = false; string activeFor = ""; if (link.Edge.ActiveZone) { activeZone = true; activeFor = link.Edge.ActiveForRunway(Runway.Designator); } else if (link.Next.Edge != null && link.Next.Edge.ActiveZone) { activeZone = true; activeFor = link.Next.Edge.ActiveForRunway(Runway.Designator); } else if (link.Next.Edge == null) { activeZone = true; activeFor = Runway.Designator; } 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; } steerPoints.Add(new RunwayPoint(entryPoint.OnRunwayNode, 8, Runway.Designator, Runway.Designator)); VortexMath.PointFrom(entryPoint.OnRunwayNode, Runway.Bearing, 0.022, ref addLat, ref addLon); steerPoints.Add(new RunwayPoint(addLat, addLon, 6, Runway.Designator, Runway.Designator)); RouteProcessor.Smooth(steerPoints); RouteProcessor.ProcessRunwayOperations(steerPoints); if (MaxOutPoints < steerPoints.Count) { MaxOutPoints = steerPoints.Count; } return(steerPoints); }
/// <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 int WriteRoutes(string outputPath, bool kml) { int count = 0; foreach (KeyValuePair <TaxiNode, Dictionary <XPlaneAircraftCategory, ResultRoute> > sizeRoutes in _results) { for (XPlaneAircraftCategory size = Parking.MaxSize; size >= XPlaneAircraftCategory.A; size--) { if (sizeRoutes.Value.ContainsKey(size)) { ResultRoute route = sizeRoutes.Value[size]; if (route.TargetNode == null) { continue; } if (Parking.MaxSize < route.MinSize) { continue; } XPlaneAircraftCategory validMax = (XPlaneAircraftCategory)Math.Min((int)route.MaxSize, (int)Parking.MaxSize); IEnumerable <WorldTrafficAircraftType> wtTypes = AircraftTypeConverter.WTTypesFromXPlaneLimits(route.MinSize, validMax, Parking.Operation); if (wtTypes.Count() == 0) { Logger.Log($"WARN {Parking.Name} (Max)Cat {Parking.MaxSize} Types: {string.Join(" ", Parking.XpTypes)} does not map to any WT types."); } if (route.AvailableRunwayLength < VortexMath.Feet5000Km) { WorldTrafficAircraftType[] big = { WorldTrafficAircraftType.SuperHeavy, WorldTrafficAircraftType.HeavyJet, WorldTrafficAircraftType.LargeJet, WorldTrafficAircraftType.LargeProp, WorldTrafficAircraftType.LightJet }; wtTypes = wtTypes.Except(big); } else if (route.AvailableRunwayLength < VortexMath.Feet6500Km) { WorldTrafficAircraftType[] big = { WorldTrafficAircraftType.SuperHeavy, WorldTrafficAircraftType.HeavyJet }; wtTypes = wtTypes.Except(big); } else if (route.AvailableRunwayLength > VortexMath.Feet8000Km) { WorldTrafficAircraftType[] small = { WorldTrafficAircraftType.LightProp, WorldTrafficAircraftType.LightJet, WorldTrafficAircraftType.MediumProp }; wtTypes = wtTypes.Except(small); } if (wtTypes.Count() == 0) { continue; } IEnumerable <SteerPoint> steerPoints = BuildSteerPoints(route, sizeRoutes.Key); if (steerPoints.Count() <= Settings.MaxSteerpoints) { string allSizes = string.Join(" ", wtTypes.Select(w => (int)w).OrderBy(w => w)); string sizeName = (wtTypes.Count() == 10) ? "all" : allSizes.Replace(" ", ""); string fileName = Path.Combine(outputPath, $"{route.Runway.Designator}_to_{Parking.FileNameSafeName}_{route.AvailableRunwayLength * VortexMath.KmToFoot:00000}_{sizeName}"); using (RouteWriter sw = RouteWriter.Create(kml ? 0 : 1, fileName, allSizes, -1, -1, route.Runway.Designator, ParkingReferenceConverter.ParkingReference(Settings.ParkingReference))) { count++; foreach (SteerPoint steerPoint in steerPoints) { sw.Write(steerPoint); } } } else { Logger.Log($"Route from <{route.Runway.Designator}> to {Parking.FileNameSafeName} not written. Too many steerpoints ({steerPoints.Count()} vs {Settings.MaxSteerpoints})"); } } } } return(count); }
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); }