public void Preprocess() { // Filter out nodes with links (probably nodes for the vehicle network) _taxiNodes = _nodeDict.Values.Where(v => v.IncomingEdges.Count > 0); // Filter out parkings with operation type none. _parkings = _parkings.Where(p => p.Operation != OperationType.None).ToList(); // With unneeded nodes gone, parse the lat/lon string and convert the values to radians foreach (TaxiNode v in _taxiNodes) { v.ComputeLonLat(); } // Compute distance and bearing of each edge foreach (TaxiEdge edge in _edges) { edge.Compute(); } Dictionary <XPlaneAircraftCategory, int> numberOfParkingsPerCategory = new Dictionary <XPlaneAircraftCategory, int>(); for (XPlaneAircraftCategory cat = XPlaneAircraftCategory.A; cat < XPlaneAircraftCategory.Max; cat++) { numberOfParkingsPerCategory[cat] = 0; } Dictionary <WorldTrafficAircraftType, int> numberOfParkingsPerWTType = new Dictionary <WorldTrafficAircraftType, int>(); for (WorldTrafficAircraftType cat = WorldTrafficAircraftType.Fighter; cat <= WorldTrafficAircraftType.Max; cat++) { numberOfParkingsPerWTType[cat] = 0; } foreach (Parking parking in _parkings) { parking.DetermineWtTypes(); parking.DetermineTaxiOutLocation(_taxiNodes); // Move this to first if we need pushback info in the parking def numberOfParkingsPerCategory[parking.MaxSize]++; foreach (XPlaneAircraftType wtt in parking.XpTypes) { WorldTrafficAircraftType t = AircraftTypeConverter.WTTypeFromXPlaneTypeAndCat(parking.MaxSize, wtt); numberOfParkingsPerWTType[t]++; } //parking.FindNearestLine(_lines); } Log($"Parkings by Category: {string.Join(" ", numberOfParkingsPerCategory.Select(kvp => kvp.Key.ToString() + ": " + kvp.Value.ToString()))}"); Log($"Parkings by WorldTraffic type:\n\t{string.Join("\n\t", numberOfParkingsPerWTType.Select(kvp => $"{kvp.Key.ToString(),-15}: {kvp.Value.ToString()}"))}".Replace("Max", "Undefined")); StringBuilder sb = new StringBuilder(); _flows.Analyze(sb); foreach (Runway r in _runways) { r.Analyze(_taxiNodes, _edges); } }
public ResultRoute(XPlaneAircraftCategory size) { Parkings = new List <Parking>(); Distance = double.MaxValue; MaxSize = size; MinSize = 0; }
/// <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="parkingNode">The runway node for this exit</param> public void AddResult(XPlaneAircraftCategory maxSizeCurrentResult, TaxiNode parkingNode, Parking parking, TaxiNode entryGroupNode, EntryPoint entryPoint) { if (!_results.ContainsKey(parkingNode)) { _results[parkingNode] = new Dictionary <XPlaneAircraftCategory, ResultRoute>(); } Dictionary <XPlaneAircraftCategory, ResultRoute> originResults = _results[parkingNode]; // If no results yet for this node, just add the current route if (originResults.Count == 0) { ResultRoute theRoute = ResultRoute.ExtractRoute(_edges, parkingNode, maxSizeCurrentResult); if (!theRoute.HasNode(entryPoint.OnRunwayNode)) { originResults.Add(maxSizeCurrentResult, theRoute); originResults[maxSizeCurrentResult].RunwayEntryPoint = entryPoint; originResults[maxSizeCurrentResult].AvailableRunwayLength = entryPoint.TakeoffLengthRemaining; } } else { XPlaneAircraftCategory minSize = originResults.Min(or => or.Key); if (originResults[minSize].Distance > parkingNode.DistanceToTarget) { if (minSize > maxSizeCurrentResult) { ResultRoute theRoute = ResultRoute.ExtractRoute(_edges, parkingNode, maxSizeCurrentResult); if (!theRoute.HasNode(entryPoint.OnRunwayNode)) { originResults.Add(maxSizeCurrentResult, theRoute); originResults[maxSizeCurrentResult].RunwayEntryPoint = entryPoint; originResults[maxSizeCurrentResult].AvailableRunwayLength = entryPoint.TakeoffLengthRemaining; originResults[minSize].MinSize = (maxSizeCurrentResult + 1); } } else if (minSize == maxSizeCurrentResult) { ResultRoute theRoute = ResultRoute.ExtractRoute(_edges, parkingNode, maxSizeCurrentResult); if (!theRoute.HasNode(entryPoint.OnRunwayNode)) { originResults[minSize] = theRoute; originResults[minSize].RunwayEntryPoint = entryPoint; originResults[minSize].AvailableRunwayLength = entryPoint.TakeoffLengthRemaining; } } } } // Nasty overkill to make sure parkings with the same 'nearest' node will have routes generated foreach (KeyValuePair <XPlaneAircraftCategory, ResultRoute> result in originResults) { result.Value.AddParking(parking); } }
public Parking(Airport airport) : base() { _airport = airport; AlternateAfterPushBack = null; PushBackLatitude = 0; PushBackLongitude = 0; MaxSize = XPlaneAircraftCategory.F; Operation = OperationType.Airline; Operators = Enumerable.Empty <string>(); }
public TaxiEdge(TaxiNode startNode, TaxiNode endNode, bool isRunway, XPlaneAircraftCategory maxSize, string linkName) { StartNode = startNode; EndNode = endNode; IsRunway = isRunway; MaxCategory = maxSize; LinkName = linkName; ActiveForRunways = new List <string>(); ActiveZone = false; ReverseEdge = null; }
public int FindOutboundRoutes(bool normalOutput, ProgressBar progress) { string outputPath = normalOutput ? Path.Combine(Settings.WorldTrafficGroundRoutes, "Departure") : Settings.DepartureFolderKML; outputPath = Path.Combine(outputPath, ICAO); Settings.DeleteDirectoryContents(outputPath); int count = 0; progress.Minimum = 0; progress.Maximum = _runways.Sum(r => r.EntryGroups.Count) * (int)XPlaneAircraftCategory.Max; progress.Value = 0; // for each runway foreach (Runway runway in _runways) { // for each takeoff spot foreach (KeyValuePair <TaxiNode, List <EntryPoint> > entryGroup in runway.EntryGroups) { OutboundResults or = new OutboundResults(_edges, runway); // for each size for (XPlaneAircraftCategory size = XPlaneAircraftCategory.F; size >= XPlaneAircraftCategory.A; size--) { foreach (EntryPoint ep in entryGroup.Value) { // find shortest path from each parking to each takeoff spot considering each entrypoint FindShortestPaths(_taxiNodes, ep.OffRunwayNode, size); foreach (Parking parking in _parkings) { or.AddResult(size, parking.NearestNode, parking, entryGroup.Key, ep); } } progress.Value++; progress.Update(); } count += or.WriteRoutes(outputPath, !normalOutput); } } progress.Maximum++; progress.Value++; progress.Maximum--; progress.Value = progress.Maximum; // Work around for a side effect caused by windows animating the progress bar progress.Update(); return(count); }
/// <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 static WorldTrafficAircraftType WTTypeFromXPlaneTypeAndCat(XPlaneAircraftCategory category, XPlaneAircraftType xpType) { switch (category) { case XPlaneAircraftCategory.A: switch (xpType) { case XPlaneAircraftType.Helo: return(WorldTrafficAircraftType.Helo); case XPlaneAircraftType.Fighter: return(WorldTrafficAircraftType.Fighter); case XPlaneAircraftType.Prop: case XPlaneAircraftType.TurboProp: return(WorldTrafficAircraftType.LightProp); case XPlaneAircraftType.Jet: return(WorldTrafficAircraftType.LightJet); default: break; } break; case XPlaneAircraftCategory.B: switch (xpType) { case XPlaneAircraftType.TurboProp: return(WorldTrafficAircraftType.MediumProp); case XPlaneAircraftType.Jet: return(WorldTrafficAircraftType.MediumJet); default: break; } break; case XPlaneAircraftCategory.C: switch (xpType) { case XPlaneAircraftType.TurboProp: return(WorldTrafficAircraftType.MediumProp); case XPlaneAircraftType.Jet: return(WorldTrafficAircraftType.LargeJet); default: break; } break; case XPlaneAircraftCategory.D: switch (xpType) { case XPlaneAircraftType.TurboProp: return(WorldTrafficAircraftType.LargeProp); case XPlaneAircraftType.HeavyJet: return(WorldTrafficAircraftType.HeavyJet); default: break; } break; case XPlaneAircraftCategory.E: switch (xpType) { case XPlaneAircraftType.HeavyJet: return(WorldTrafficAircraftType.HeavyJet); default: break; } break; case XPlaneAircraftCategory.F: switch (xpType) { case XPlaneAircraftType.HeavyJet: return(WorldTrafficAircraftType.SuperHeavy); default: break; } break; } return(WorldTrafficAircraftType.Max); }
public static IEnumerable <WorldTrafficAircraftType> WTTypesFromXPlaneLimits(XPlaneAircraftCategory minCategory, XPlaneAircraftCategory maxCategory, OperationType operationType) { List <WorldTrafficAircraftType> wtTypes = new List <WorldTrafficAircraftType>() { WorldTrafficAircraftType.Fighter }; for (XPlaneAircraftCategory cat = minCategory; cat <= maxCategory; cat++) { switch (cat) { case XPlaneAircraftCategory.A: switch (operationType) { case OperationType.Airline: case OperationType.Cargo: case OperationType.GeneralAviation: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.LightJet, WorldTrafficAircraftType.LightProp }); break; case OperationType.Military: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.LightJet, WorldTrafficAircraftType.LightProp, WorldTrafficAircraftType.Fighter }); break; case OperationType.None: default: break; } break; case XPlaneAircraftCategory.B: switch (operationType) { case OperationType.Airline: case OperationType.Cargo: case OperationType.GeneralAviation: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.MediumJet, WorldTrafficAircraftType.MediumProp, WorldTrafficAircraftType.LightJet, WorldTrafficAircraftType.LightProp }); break; case OperationType.Military: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.MediumJet, WorldTrafficAircraftType.MediumProp, WorldTrafficAircraftType.LightJet, WorldTrafficAircraftType.LightProp, WorldTrafficAircraftType.Fighter }); break; case OperationType.None: default: break; } break; case XPlaneAircraftCategory.C: switch (operationType) { case OperationType.Airline: case OperationType.Cargo: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.LargeJet, WorldTrafficAircraftType.LargeProp }); break; case OperationType.GeneralAviation: case OperationType.Military: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.MediumJet, WorldTrafficAircraftType.MediumProp, WorldTrafficAircraftType.LargeJet, WorldTrafficAircraftType.LargeProp }); break; case OperationType.None: default: break; } break; case XPlaneAircraftCategory.D: switch (operationType) { case OperationType.Airline: case OperationType.Cargo: case OperationType.GeneralAviation: case OperationType.Military: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.LargeJet, WorldTrafficAircraftType.HeavyJet, WorldTrafficAircraftType.LargeProp }); break; case OperationType.None: default: break; } break; case XPlaneAircraftCategory.E: switch (operationType) { case OperationType.Airline: case OperationType.Cargo: case OperationType.GeneralAviation: case OperationType.Military: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.HeavyJet, WorldTrafficAircraftType.LargeProp }); break; case OperationType.None: default: break; } break; case XPlaneAircraftCategory.F: switch (operationType) { case OperationType.Airline: case OperationType.Cargo: case OperationType.GeneralAviation: case OperationType.Military: wtTypes.AddRange(new WorldTrafficAircraftType[] { WorldTrafficAircraftType.HeavyJet, WorldTrafficAircraftType.SuperHeavy }); break; case OperationType.None: default: break; } break; } } return(wtTypes.Distinct()); }
internal void SetMetaData(XPlaneAircraftCategory maxSize, string operation, IEnumerable <string> operators) { MaxSize = maxSize; Operation = OperationTypeConverter.FromString(operation); Operators = operators; }
/// <summary> /// Dijkstra... goes through the full network finding shortest path from every node to the target so /// that afterwards we can cherrypick the starting nodes we are actually interested in. /// </summary> /// <param name="nodes">The node network</param> /// <param name="targetNode">Here do we go now</param> /// <param name="targetCategory">Minimum Cat (A-F) that needs to be supported by the route</param> private static void FindShortestPaths(IEnumerable <TaxiNode> nodes, TaxiNode targetNode, XPlaneAircraftCategory targetCategory) { List <TaxiNode> untouchedNodes = nodes.ToList(); List <TaxiNode> touchedNodes = new List <TaxiNode>(); // Reset previously found paths foreach (TaxiNode node in nodes) { node.DistanceToTarget = double.MaxValue; node.NextNodeToTarget = null; } // Setup the targetnode targetNode.DistanceToTarget = 0; targetNode.NextNodeToTarget = null; // Assign distances to all incoming edges of the target foreach (TaxiEdge incoming in targetNode.IncomingEdges) { // Skip taxiways that are too small if (targetCategory > incoming.MaxCategory) { continue; } // Mark the other side of the incoming edge as touched... if (untouchedNodes.Contains(incoming.StartNode) && !touchedNodes.Contains(incoming.StartNode)) { untouchedNodes.Remove(incoming.StartNode); touchedNodes.Add(incoming.StartNode); } // And set the properties of the path incoming.StartNode.DistanceToTarget = incoming.DistanceKM; incoming.StartNode.NextNodeToTarget = targetNode; incoming.StartNode.NameToTarget = incoming.LinkName; incoming.StartNode.PathIsRunway = incoming.IsRunway; incoming.StartNode.BearingToTarget = VortexMath.BearingRadians(incoming.StartNode, targetNode); } // Remove the target node completely, it's done. untouchedNodes.Remove(targetNode); // instantiate the comparer for taxinode list sorting ShortestPathComparer spc = new ShortestPathComparer(); // Now rinse and repeat will we still have 'touched nodes' (unprocessed nodes with a path to the target) while (touchedNodes.Count() > 0) { // Pick the next( touched but not finished) node to process, that is: the one which currently has the shortest path to the target touchedNodes.Sort(spc); TaxiNode currentNode = touchedNodes.First(); // And set the distances for the nodes with link towards the current node foreach (TaxiEdge incoming in currentNode.IncomingEdges) { // Skip taxiways that are too small if (targetCategory > incoming.MaxCategory) { continue; } // Avoid runways unless they are the only option. double distanceToCurrent = incoming.DistanceKM; if (incoming.IsRunway) { distanceToCurrent += 2.0; } // If the incoming link + the distance from the current node to the target is smaller // than the so far shortest distance from the node on the otherside of the incoming link to // the target... reroute the path from the otherside through the current node. if ((distanceToCurrent + currentNode.DistanceToTarget) < incoming.StartNode.DistanceToTarget) { if (untouchedNodes.Contains(incoming.StartNode) && !touchedNodes.Contains(incoming.StartNode)) { // The 'otherside' node is now ready to be processed untouchedNodes.Remove(incoming.StartNode); touchedNodes.Add(incoming.StartNode); } // Update the path properties incoming.StartNode.DistanceToTarget = (currentNode.DistanceToTarget + distanceToCurrent); incoming.StartNode.NextNodeToTarget = currentNode; incoming.StartNode.NameToTarget = incoming.LinkName; incoming.StartNode.PathIsRunway = incoming.IsRunway; incoming.StartNode.BearingToTarget = incoming.Bearing; } } // And the current is done. touchedNodes.Remove(currentNode); } }
public int FindInboundRoutes(bool normalOutput, ProgressBar progress) { string outputPath = normalOutput ? Path.Combine(Settings.WorldTrafficGroundRoutes, "Arrival") : Settings.ArrivalFolderKML; outputPath = Path.Combine(outputPath, ICAO); Settings.DeleteDirectoryContents(outputPath); int count = 0; progress.Minimum = 0; progress.Maximum = _parkings.Count * (int)XPlaneAircraftCategory.Max; progress.Value = 0; foreach (Parking parking in _parkings) { InboundResults ir = new InboundResults(_edges, parking); progress.Value += (XPlaneAircraftCategory.Max - (parking.MaxSize + 1)); for (XPlaneAircraftCategory size = parking.MaxSize; size >= XPlaneAircraftCategory.A; size--) { // Nearest node should become 'closest to computed pushback point' FindShortestPaths(_taxiNodes, parking.NearestNode, size); // Pick the runway exit points for the selected size foreach (Runway r in _runways) { foreach (KeyValuePair <TaxiNode, List <ExitPoint> > exit in r.ExitGroups) { double bestDistance = double.MaxValue; double bestTurnAngle = double.MaxValue; ExitPoint bestExit = null; foreach (ExitPoint ep in exit.Value) { if (ep.OffRunwayNode.NextNodeToTarget != ep.OnRunwayNode) { if ((ep.OffRunwayNode.DistanceToTarget < bestDistance / 1.2) || (bestTurnAngle - Math.Abs(ep.TurnAngle) > VortexMath.Deg020Rad) || (ep.OffRunwayNode.DistanceToTarget < bestDistance && Math.Abs(ep.TurnAngle) < bestTurnAngle)) { bestExit = ep; bestDistance = ep.OffRunwayNode.DistanceToTarget; bestTurnAngle = Math.Abs(ep.TurnAngle); } } } if (bestExit != null) { ir.AddResult(size, bestExit.OnRunwayNode, bestExit.OffRunwayNode, r, bestExit.LandingLengthUsed); } } } progress.Value++; progress.Update(); } count += ir.WriteRoutes(outputPath, !normalOutput); } progress.Maximum++; progress.Value++; progress.Maximum--; progress.Value = progress.Maximum; // Work around for a side effect caused by windows animating the progress bar progress.Update(); return(count); }
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); }
/// <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); }