protected Func <TVertex, double> DistancesIndexGetter()
 {
     return(AlgorithmExtensions.GetIndexer <TVertex, double>(this.distances));
 }
Beispiel #2
0
        internal ILineString PerformShortestPathAnalysis(IPoint source, IPoint destination, bool usesCondensedGraph)
        {
            // We keep  a copy so we can terminate the search early.
            _theDestination = destination;

            // This is an instrance of the shortest path algortihm.
            _dijkstra = new DijkstraShortestPathAlgorithm <Coordinate, Edge <Coordinate> >(_graph, AlgorithmExtensions.GetIndexer(_edgeCost));

            // Quick Graph uses 'observers'  to record the distance traveled and the path travelled througth,
            var distObserver        = new VertexDistanceRecorderObserver <Coordinate, Edge <Coordinate> >(AlgorithmExtensions.GetIndexer(_edgeCost));
            var predecessorObserver = new VertexPredecessorRecorderObserver <Coordinate, Edge <Coordinate> >();

            distObserver.Attach(_dijkstra);
            predecessorObserver.Attach(_dijkstra);

            // Having this present means that when we finally reach the target node
            // the dijkstra algortihm can quit without scanning other nodes in the graph, leading to
            // performance increases.
            _dijkstra.FinishVertex += dijkstra_FinishVertex;


            // This is where the shortest path is calculated.
            var sw = new System.Diagnostics.Stopwatch();

            sw.Start();
            _dijkstra.Compute(source.Coordinate);
            sw.Stop();
            System.Diagnostics.Debug.WriteLine("Dijsktra Took: " + sw.ElapsedMilliseconds);

            // get the cost of the path. If one is found then d (=distance) should be greater than zero so we get the edges making up
            // the path.
            double d = AlgorithmExtensions.ComputePredecessorCost(predecessorObserver.VertexPredecessors, _edgeCost, destination.Coordinate);

            System.Diagnostics.Debug.WriteLine(d);

            if (d > 0)
            {
                IEnumerable <Edge <Coordinate> > edgesInShortestPath;
                if (predecessorObserver.TryGetPath(destination.Coordinate, out edgesInShortestPath))
                {
                    var theCompleteShortestPath = new List <Coordinate>();

                    // We need to use a different approach when using the condensed graph.
                    if (usesCondensedGraph)
                    {
                        foreach (var edgeInShortPath in edgesInShortestPath)
                        {
                            var ls = GetLineStringInformation(edgeInShortPath);

                            if (ls != null)
                            {
                                // We need to get each of the nodes that makes up the lines.
                                // we need to add each of them on one list.
                                var count = ls.Coordinates.Length;

                                for (var i = 0; i < count; i++)
                                {
                                    theCompleteShortestPath.Add(ls.Coordinates[i]);
                                }
                            } // End Of If
                        }     // Loop Around Each Edge In The Shortest Path
                    }         // End of If
                    else
                    {
                        foreach (var edgeInShortPath in edgesInShortestPath)
                        {
                            theCompleteShortestPath.Add(edgeInShortPath.Source);
                            theCompleteShortestPath.Add(edgeInShortPath.Target);
                        }
                    } // End Of Else

                    var theShortestPath = _geomFactory.CreateLineString(theCompleteShortestPath.ToArray());
                    return(theShortestPath);
                } // There Was A Shortest Path

                // Return null.
                // ToDo: Need to improve this bit so it at least indicates if the SP didnt get a path/
                return(null);
            }

            return(null);
        }
Beispiel #3
0
        public static void Create(List <Node> Nodes, List <Way> Ways)
        {
            //create the graph
            var Graph = new BidirectionalGraph <int, CompEdge>();

            //add each node referenced by a way
            foreach (Way Path in Ways)
            {
                for (int i = 0; i < Path.References.Count; i++)
                {
                    int Id = (int)Path.References[i].Id;

                    // make sure the node was not already added
                    if (!Graph.Vertices.Contains(Id))
                    {
                        Graph.AddVertex(Id);

                        short Altitude = AltitudeMap.AltitudeAtPosition(Path.References[i].Location);

                        NodeAltitudes.Add(Id, (int)Altitude);
                    }
                }
            }


            //create an altitude map
            Bitmap AltitudeImage = new Bitmap(64, 64, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            List <double> LongitudeGradient = Gradient(Program.LowerBound.Longitude, Program.UpperBound.Longitude, 64);
            List <double> LatitudeGradient  = Gradient(Program.LowerBound.Latitude, Program.UpperBound.Latitude, 64);



            //iterate over altitudes
            for (int longitudeindex = 0; longitudeindex < LongitudeGradient.Count; longitudeindex++)
            {
                for (int latitudeindex = 0; latitudeindex < LatitudeGradient.Count; latitudeindex++)
                {
                    short Altitude = AltitudeMap.AltitudeAtPosition(new GeoCoordinate(LatitudeGradient[latitudeindex], LongitudeGradient[longitudeindex]));

                    AltitudeImage.SetPixel(longitudeindex, latitudeindex, Color.FromArgb(Altitude, Altitude, Altitude));
                }
            }


            // setup the bitmap that all of the data will be saved to
            AltitudeImage.Save("RegionalAltitudeImage.bmp");

            //add each edge
            foreach (Way Path in Ways)
            {
                //add all of the edges
                for (int i = 1; i < Path.References.Count; i++)
                {
                    Node FirstNode  = Path.References[i - 1];
                    Node SecondNode = Path.References[i];

                    //add the edge
                    Graph.AddEdge(new CompEdge((int)FirstNode.Id, (int)SecondNode.Id));
                    Graph.AddEdge(new CompEdge((int)SecondNode.Id, (int)FirstNode.Id));

                    //add the distance edge
                    double Distance = DistanceBetweenPlaces(FirstNode.Location, SecondNode.Location);

                    //add both distances
                    Distances.Add(new CompEdge((int)FirstNode.Id, (int)SecondNode.Id), Distance);
                    Distances.Add(new CompEdge((int)SecondNode.Id, (int)FirstNode.Id), Distance);

                    //get the altitudes
                    double FirstNodeAltitude  = NodeAltitudes[(int)FirstNode.Id];
                    double SecondNodeAltitude = NodeAltitudes[(int)SecondNode.Id];

                    //if we don't know either of the altitudes, assume 0 change
                    if (FirstNodeAltitude != -1 || SecondNodeAltitude != -1)
                    {
                        AltitudeChange.Add(new CompEdge((int)FirstNode.Id, (int)SecondNode.Id), FirstNodeAltitude - SecondNodeAltitude);
                        AltitudeChange.Add(new CompEdge((int)SecondNode.Id, (int)FirstNode.Id), SecondNodeAltitude - FirstNodeAltitude);
                    }
                    else
                    {
                        AltitudeChange.Add(new CompEdge((int)FirstNode.Id, (int)SecondNode.Id), 0);
                        AltitudeChange.Add(new CompEdge((int)SecondNode.Id, (int)FirstNode.Id), 0);
                    }
                }
            }

            //calcuate costs for each edge
            var Costs = new Dictionary <CompEdge, double>();

            //cycle through each edge and calculate its cost
            foreach (Edge <int> Edge in Graph.Edges)
            {
                CompEdge Key = new CompEdge(Edge.Source, Edge.Target);

                double DistanceCost = Distances[Key] * 1000;            //convert to meters
                double AltitudeCost = Math.Pow(AltitudeChange[Key], 2); //square the altitude (in meters)

                double Weight = DistanceCost;
                Costs.Add(Key, Weight);
            }


            //create arrays that the data will be stored in
            string[] NodeLines = new string[Graph.Vertices.Count()];
            string[] EdgeLines = new string[Graph.Edges.Count()];

            //save nodes to a .csv file
            for (int i = 0; i < Graph.Vertices.Count(); i++)
            {
                int Id = Graph.Vertices.ElementAt(i);

                NodeLines[i] = Id + "," + Node.GetById(Id).Location.Latitude + "," + Node.GetById(Id).Location.Longitude;
            }

            //save edges to a .csv file
            for (int i = 0; i < Graph.Edges.Count(); i++)
            {
                int Source = Graph.Edges.ElementAt(i).Source;
                int Target = Graph.Edges.ElementAt(i).Target;

                EdgeLines[i] = Source + "," + Target + "," + Costs[new CompEdge(Source, Target)];
            }

            File.WriteAllLines("nodes.csv", NodeLines);
            File.WriteAllLines("edges.csv", EdgeLines);


            int From = 0;
            int To   = 5;


            var CostIndexer = AlgorithmExtensions.GetIndexer(Costs);
            var tryGetPath  = Graph.ShortestPathsDijkstra(CostIndexer, From);

            IEnumerable <CompEdge> path;

            if (tryGetPath(To, out path))
            {
                string ToWrite = "";

                foreach (CompEdge Edge in path)
                {
                    ToWrite += Convert.ToString(Edge.Source) + ",";
                }

                File.WriteAllText("chosenpath.csv", ToWrite.Remove(ToWrite.Length - 1)); // remove the last comma
            }

            //create a visualizer for the graph
            //GraphvizAlgorithm<int, CompEdge> graphviz = new GraphvizAlgorithm<int, CompEdge>(Graph);
            //string output = "output.dot";
            //graphviz.Generate(new FileDotEngine(), output);

            // assumes dot.exe is on the path:
            //var args = string.Format(@"{0} -Tjpg -O", output);
            //System.Diagnostics.Process.Start(@"C:\Program Files (x86)\Graphviz2.38\bin\dot.exe", args);
        }
Beispiel #4
0
        public List <FloorTile> getPath()
        {
            List <FloorTile> retval = new List <FloorTile>();

            if (this.fp == null || this.fp.getStartTile() == null || this.fp.getTargetTile() == null)
            {
                return(retval);
            }

            startPoint  = this.fp.getStartTile().Position.X + "_" + this.fp.getStartTile().Position.Y;
            targetPoint = this.fp.getTargetTile().Position.X + "_" + this.fp.getTargetTile().Position.Y;



            this.messages += "- Start Get Path\n";
            //startPoint = txtStartPoint.Text;
            //targetPoint = txtTargetPoint.Text;

            DijkstraShortestPathAlgorithm <string, Edge <string> > dijkstra = new DijkstraShortestPathAlgorithm <string, Edge <string> >(graph, AlgorithmExtensions.GetIndexer <Edge <string>, double>(edgeCost));

            // Attach a Vertex Predecessor Recorder Observer to give us the paths
            QuickGraph.Algorithms.Observers.VertexPredecessorRecorderObserver <string, Edge <string> > predecessorObserver = new QuickGraph.Algorithms.Observers.VertexPredecessorRecorderObserver <string, Edge <string> >();
            predecessorObserver.Attach(dijkstra);

            // attach a distance observer to give us the shortest path distances
            VertexDistanceRecorderObserver <string, Edge <string> > distObserver = new VertexDistanceRecorderObserver <string, Edge <string> >(AlgorithmExtensions.GetIndexer <Edge <string>, double>(edgeCost));

            distObserver.Attach(dijkstra);

            // Run the algorithm with A set to be the source
            dijkstra.Compute(startPoint);
            this.messages += "    Start Point: " + startPoint + ".\n";
            this.messages += "    Target Point: " + targetPoint + ".\n";

            String outString = "";

            //outString += distObserver.Distances[targetPoint] + "\n";

            IEnumerable <Edge <string> > path;

            if (predecessorObserver.TryGetPath(targetPoint, out path))
            {
                foreach (var u in path)
                {
                    outString += u + ";";
                }
            }



            string[] outEdges = Regex.Split(outString, ";");
            if (outEdges.Length > 0)
            {
                for (int i = 0; i < outEdges.Length; i++)
                {
                    if (outEdges[i].Length > 0)
                    {
                        this.messages += outEdges[i] + "\n";
                        string[] outPoint = Regex.Split(outEdges[i], "->");
                        //start points
                        retval.Add(getTileByIndex(fp, outPoint[0]));
                    }
                }
                //add target
                retval.Add(getTileByIndex(fp, targetPoint));
            }

            this.messages += retval.Count.ToString() + "\n";;

            if (retval.Count == 1 && retval[0].Equals(getTileByIndex(fp, targetPoint)))
            {
                this.messages += "Can't find path. Start or end point is not walkable or no available walkable tiles" + "\n";;
                return(null);
            }

            fp.setPath(retval);
            //return retval;

            return(condenseList(retval));
        }
        public void DijkstraSimpleGraph2()
        {
            var graph     = new AdjacencyGraph <char, Edge <char> >();
            var distances = new Dictionary <Edge <char>, double>();

            graph.AddVertexRange("ABCDE");
            AddEdge('A', 'C', 1);
            AddEdge('B', 'B', 2);
            AddEdge('B', 'D', 1);
            AddEdge('B', 'E', 2);
            AddEdge('C', 'B', 7);
            AddEdge('C', 'D', 3);
            AddEdge('D', 'E', 1);
            AddEdge('E', 'A', 1);
            AddEdge('E', 'B', 1);

            var algorithm    = new DijkstraShortestPathAlgorithm <char, Edge <char> >(graph, AlgorithmExtensions.GetIndexer(distances));
            var predecessors = new VertexPredecessorRecorderObserver <char, Edge <char> >();

            using (predecessors.Attach(algorithm))
                algorithm.Compute('A');

            Assert.AreEqual(0, algorithm.GetDistance('A'));
            Assert.AreEqual(6, algorithm.GetDistance('B'));
            Assert.AreEqual(1, algorithm.GetDistance('C'));
            Assert.AreEqual(4, algorithm.GetDistance('D'));
            Assert.AreEqual(5, algorithm.GetDistance('E'));

            #region Local function

            void AddEdge(char source, char target, double weight)
            {
                var edge = new Edge <char>(source, target);

                distances[edge] = weight;
                graph.AddEdge(edge);
            }

            #endregion
        }
Beispiel #6
0
        public static void ValidatePathsUsingDijkstra(Topology.IGP.Topology igp_topology, Topology.MPLS.HierarchicalLabelSwitchedPath.HierarchicalLabelSwitchedPath hlsp)
        {
            Console.WriteLine("\n====Validating Paths====\n");

            List <yggdrasil2.Topology.Node.Node>     nodes_copy = igp_topology.Nodes.DeepClone();
            List <yggdrasil2.Topology.IGP.Link.Link> links_copy = igp_topology.Links.DeepClone();

            BidirectionalGraph <string, TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link> > graph =
                new BidirectionalGraph <string, TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link> >();

            Dictionary <TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link>, double> cost =
                new Dictionary <TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link>, double>();


            var start = nodes_copy.Where(n => n.IPv4RouterIdentifier == hlsp.IPv4TunnelSenderAddress).SingleOrDefault();
            var end   = nodes_copy.Where(n => n.IPv4RouterIdentifier == hlsp.IPv4TunnelEndpointAddress).SingleOrDefault();

            if (start != null && end != null)
            {
                foreach (var lsp in hlsp.Children)
                {
                    graph.Clear();

                    foreach (var node in nodes_copy)
                    {
                        graph.AddVertex(node.Id);
                    }

                    if (!start.IsPseudonode)  // it will never be a pseudonode, get rid of this
                    {
                        var nodeLinks = links_copy.Where(l => l.SourceNode == start.Id).ToList();

                        foreach (var l in nodeLinks)
                        {
                            if (!graph.ContainsEdge(l.SourceNode, l.TargetNode))
                            {
                                if (l.OperationalStatus)
                                {
                                    var forwardEdge = new TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link>(l.SourceNode, l.TargetNode, l);
                                    graph.AddEdge(forwardEdge);
                                    cost.Add(forwardEdge, 1);
                                }
                            }
                        }
                    }

                    foreach (var hop in lsp.ComputedExplicitRouteObject)
                    {
                        var link = links_copy.Where(l => l.IPv4InterfaceAddress == hop).SingleOrDefault();

                        if (link != null)
                        {
                            if (!graph.ContainsEdge(link.SourceNode, link.TargetNode))
                            {
                                if (link.OperationalStatus)
                                {
                                    var backwardEdge = new TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link>(link.TargetNode, link.SourceNode, link);
                                    graph.AddEdge(backwardEdge);
                                    cost.Add(backwardEdge, 1);
                                }

                                //var srcNode = nodes_copy.Where(n => n.IgpRouterIdentifier == link.SourceNode).SingleOrDefault();
                                var dstNode = nodes_copy.Where(n => n.IgpRouterIdentifier == link.TargetNode).SingleOrDefault();

                                //if (srcNode != null)
                                //{

                                //    if (srcNode.IsPseudonode)
                                //    {
                                //        var nodeLinks = links_copy.Where(l => l.TargetNode == srcNode.Id).ToList();

                                //        foreach (var l in nodeLinks)
                                //        {

                                //            if (!graph.ContainsEdge(l.SourceNode, l.TargetNode))
                                //            {
                                //                if (l.OperationalStatus)
                                //                {
                                //                    var forwardEdge = new TaggedEdge<string, yggdrasil2.Topology.IGP.Link.Link>(l.SourceNode, l.TargetNode, l);
                                //                    graph.AddEdge(forwardEdge);
                                //                    cost.Add(forwardEdge, 1);
                                //                }
                                //            }
                                //        }
                                //    }

                                //}

                                if (dstNode != null)
                                {
                                    if (dstNode.IsPseudonode)
                                    {
                                        var nodeLinks = links_copy.Where(l => l.TargetNode == dstNode.Id).ToList();

                                        foreach (var l in nodeLinks)
                                        {
                                            if (!graph.ContainsEdge(l.SourceNode, l.TargetNode))
                                            {
                                                if (l.OperationalStatus)
                                                {
                                                    var forwardEdge = new TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link>(l.SourceNode, l.TargetNode, l);
                                                    graph.AddEdge(forwardEdge);
                                                    cost.Add(forwardEdge, 1);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    DijkstraShortestPathAlgorithm <string, TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link> > dijkstra =
                        new DijkstraShortestPathAlgorithm <string, TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link> >(graph,
                                                                                                                            AlgorithmExtensions.GetIndexer <TaggedEdge <string, yggdrasil2.Topology.IGP.Link.Link>, double>(cost));

                    dijkstra.Compute(start.Id);

                    if (dijkstra.Distances.ContainsKey(end.Id) && dijkstra.Distances[end.Id] != double.MaxValue)
                    {
                        lsp.Feasible = true;
                        Console.WriteLine("Path {0} is \u001b[32mFEASIBLE\u001b[0m.\n\t{1} is REACHABLE from {2} in {3} hops (includes pseudonodes).",
                                          lsp.SymbolicPathName, lsp.IPv4TunnelEndpointAddress, lsp.IPv4TunnelSenderAddress, dijkstra.Distances[end.Id]);
                    }
                    else
                    {
                        lsp.Feasible = false;
                        Console.WriteLine("Path {0} is \u001b[31mNOT FEASIBLE\u001b[0m.\n\t{1} is UREACHABLE from {2}.",
                                          lsp.SymbolicPathName, lsp.IPv4TunnelEndpointAddress, lsp.IPv4TunnelSenderAddress);
                    }
                }
            }
        }
Beispiel #7
0
        public void Compute()
        {
            var g         = new AdjacencyGraph <char, Edge <char> >();
            var distances = new Dictionary <Edge <char>, double>();

            g.AddVertexRange("ABCDE");
            AddEdge(g, distances, 'A', 'C', 1);
            AddEdge(g, distances, 'B', 'B', 2);
            AddEdge(g, distances, 'B', 'D', 1);
            AddEdge(g, distances, 'B', 'E', 2);
            AddEdge(g, distances, 'C', 'B', 7);
            AddEdge(g, distances, 'C', 'D', 3);
            AddEdge(g, distances, 'D', 'E', 1);
            AddEdge(g, distances, 'E', 'A', 1);
            AddEdge(g, distances, 'E', 'B', 1);

            var dijkstra     = new DijkstraShortestPathAlgorithm <char, Edge <char> >(g, AlgorithmExtensions.GetIndexer(distances));
            var predecessors = new VertexPredecessorRecorderObserver <char, Edge <char> >();

            using (predecessors.Attach(dijkstra))
                dijkstra.Compute('A');

            Assert.AreEqual(0, dijkstra.Distances['A']);
            Assert.AreEqual(6, dijkstra.Distances['B']);
            Assert.AreEqual(1, dijkstra.Distances['C']);
            Assert.AreEqual(4, dijkstra.Distances['D']);
            Assert.AreEqual(5, dijkstra.Distances['E']);
        }
Beispiel #8
0
        static void Main(string[] args)
        {
            var graph = new UndirectedGraph <TaxiPoint, TaggedEdge <TaxiPoint, string> >();

            // Krymsk Airfield
            TaxiPoint runwayFour = new TaxiPoint("Runway 4");
            TaxiPoint adJunction = new TaxiPoint("Alpha / Delta Junction");
            TaxiPoint acJunction = new TaxiPoint("Alpha / Charlie Junction");
            TaxiPoint bdJunction = new TaxiPoint("Bravo / Delta Junction");
            TaxiPoint padsFourteenThroughTwenty = new TaxiPoint("Pads 14 to 20");

            graph.AddVertex(runwayFour);
            graph.AddVertex(adJunction);
            graph.AddVertex(acJunction);
            graph.AddVertex(bdJunction);
            graph.AddVertex(padsFourteenThroughTwenty);

            TaggedEdge <TaxiPoint, string> runwayFourToAdJunction = new TaggedEdge <TaxiPoint, string>(runwayFour, adJunction, "Alpha");
            TaggedEdge <TaxiPoint, string> adJunctionToAcJunction = new TaggedEdge <TaxiPoint, string>(adJunction, acJunction, "Alpha");
            TaggedEdge <TaxiPoint, string> adJunctionToBdJunction = new TaggedEdge <TaxiPoint, string>(adJunction, bdJunction, "Delta");
            TaggedEdge <TaxiPoint, string> acJunctionToPadsFourteenThroughTwenty = new TaggedEdge <TaxiPoint, string>(acJunction, padsFourteenThroughTwenty, "Charlie");

            graph.AddEdge(runwayFourToAdJunction);
            graph.AddEdge(adJunctionToAcJunction);
            graph.AddEdge(adJunctionToBdJunction);
            graph.AddEdge(acJunctionToPadsFourteenThroughTwenty);

            Dictionary <TaggedEdge <TaxiPoint, string>, double> edgeCostDictionary = new Dictionary <TaggedEdge <TaxiPoint, string>, double>(graph.EdgeCount)
            {
                { runwayFourToAdJunction, 1 },
                { adJunctionToAcJunction, 1 },
                { adJunctionToBdJunction, 1 },
                { acJunctionToPadsFourteenThroughTwenty, 1 }
            };

            string dotGraph = graph.ToGraphviz(algorithm =>
            {
                // Custom init example
                algorithm.FormatVertex += (sender, vertexArgs) =>
                {
                    vertexArgs.VertexFormat.Label = $"{vertexArgs.Vertex.Name}";
                };
                algorithm.FormatEdge += (sender, edgeArgs) =>
                {
                    var label = new QuikGraph.Graphviz.Dot.GraphvizEdgeLabel
                    {
                        Value = $"Taxiway {edgeArgs.Edge.Tag} : Cost {edgeCostDictionary[edgeArgs.Edge]}"
                    };
                    edgeArgs.EdgeFormat.Label = label;
                };
            });

            Console.WriteLine("Graph Definition");
            Console.WriteLine("-------------------------------------");
            Console.WriteLine(dotGraph);
            Console.WriteLine("-------------------------------------");
            Console.WriteLine("Shortest Path Test");

            TaxiPoint root = padsFourteenThroughTwenty;

            Func <TaggedEdge <TaxiPoint, string>, double> edgeCost = AlgorithmExtensions.GetIndexer(edgeCostDictionary);

            TryFunc <TaxiPoint, IEnumerable <TaggedEdge <TaxiPoint, string> > > tryGetPaths = graph.ShortestPathsDijkstra(edgeCost, root);

            // Query path for given vertices
            TaxiPoint target = runwayFour;

            Console.WriteLine("Getting Path");

            if (tryGetPaths(target, out IEnumerable <TaggedEdge <TaxiPoint, string> > path))
            {
                List <string> taxiways = new List <string>();
                foreach (TaggedEdge <TaxiPoint, string> edge in path)
                {
                    taxiways.Add(edge.Tag);
                }

                Console.WriteLine("{0} to {1} via taxiways {2}", root.Name, target.Name, string.Join(", ", RemoveRepeating(taxiways)));
            }
            else
            {
                Console.WriteLine("Path was null");
            }

            Console.WriteLine("Press Enter to close");
            Console.ReadLine();
        }