Example #1
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="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;
                    }
                }
            }
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #4
0
        /// <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);
        }
Example #5
0
        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);
        }
Example #6
0
        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);
        }