/// <summary> /// Stellt die Referenzen auf LineNodes wieder her /// (auszuführen nach XML-Deserialisierung) /// </summary> /// <param name="saveVersion">Version der gespeicherten Datei</param> /// <param name="nodesList">Liste der bereits wiederhergestellten LineNodes</param> public void RecoverFromLoad(int saveVersion, List <LineNode> nodesList) { // Workaround um falsche Wunschgeschwindigkeiten aus alten Dateien zu korrigieren if (saveVersion < 1) { m_wunschgeschwindigkeit *= 2; } foreach (LineNode ln in nodesList) { if (startNodeHashes.Contains(ln.GetHashCode())) { startNodes.Add(ln); } else if (endNodeHashes.Contains(ln.GetHashCode())) { endNodes.Add(ln); } // dies hier ist nur zur Abwärtskompatibilität von alten Speicherständen: if (ln.GetHashCode() == startNodeHash) { startNode = ln; } else if (ln.GetHashCode() == endNodeHash) { endNode = ln; } } // dies hier ist nur zur Abwärtskompatibilität von alten Speicherständen: if (startNode != null && !startNodes.Contains(startNode)) { startNodes.Add(startNode); } if (endNode != null && !endNodes.Contains(endNode)) { endNodes.Add(endNode); } }
/// <summary> /// erstellt eine neue NodeConnection /// </summary> /// <param name="startNode">Anfangsknoten</param> /// <param name="endNode">Endknoten</param> /// <param name="ls">LineSegment</param> /// <param name="priority">Priorität</param> /// <param name="targetVelocity">target velocity</param> /// <param name="carsAllowed">Flag, ob Autos auf dieser NodeConnection erlaubt sind</param> /// <param name="busAllowed">Flag, ob Busse auf dieser NodeConnection erlaubt sind</param> /// <param name="tramAllowed">Flag, ob Straßenbahnen auf dieser NodeConnection erlaubt sind</param> /// <param name="enableIncomingLineChange">Flag, ob Spurwechsel auf diese NodeConnection erlaubt sind</param> /// <param name="enableOutgoingLineChange">Flag, ob Spurwechsel von dieser NodeConnection erlaubt sind</param> public NodeConnection( LineNode startNode, LineNode endNode, LineSegment ls, int priority, double targetVelocity, bool carsAllowed, bool busAllowed, bool tramAllowed, bool enableIncomingLineChange, bool enableOutgoingLineChange) { // TODO: NodeConnections werden stets mit lineSegment = null initialisiert? Warum? this.startNode = startNode; this.endNode = endNode; lineSegment = ls; this._priority = priority; this._targetVelocity = targetVelocity; this._carsAllowed = carsAllowed; this._busAllowed = busAllowed; this._tramAllowed = tramAllowed; this._enableIncomingLineChange = enableIncomingLineChange; this._enableOutgoingLineChange = enableOutgoingLineChange; UpdatePen(); intersectionComparer = delegate(Intersection a, Intersection b) { bool aA = (this == a._aConnection); bool bA = (this == b._aConnection); if (aA && bA) return a._aTime.CompareTo(b._aTime); else if (!aA && bA) return a._bTime.CompareTo(b._aTime); else if (aA && !bA) return a._aTime.CompareTo(b._bTime); else return a._bTime.CompareTo(b._bTime); }; _intersections = new SortedLinkedList<Intersection>(intersectionComparer); statistics = new Statistics[1]; }
/// <summary> /// erstellt ein neues LineChangeInterval /// </summary> /// <param name="targetNode">Zielknoten, der über Spurwechsel erreicht werden soll</param> /// <param name="startArcPos">Bogenlängenposition, ab dem Spurwechsel zum targetNode möglich sind</param> /// <param name="endArcPos">Bogenlängenposition, bis zu dem Spurwechsel zum targetNode möglich sind</param> public LineChangeInterval(LineNode targetNode, double startArcPos, double endArcPos) { this.targetNode = targetNode; this.startArcPos = startArcPos; this.endArcPos = endArcPos; }
/// <summary> /// Stellt die Start- und EndNode der NodeConnection anhand der Hashes und der übergebenen Line Liste wieder her /// </summary> /// <param name="saveVersion">Version der gespeicherten Datei</param> /// <param name="nodesList">Eine Liste mit sämtlichen existierenden Linien</param> public void RecoverFromLoad(int saveVersion, List<LineNode> nodesList) { startNode = GetLineNodeByHash(nodesList, startNodeHash); endNode = GetLineNodeByHash(nodesList, endNodeHash); UpdatePen(); }
/// <summary> /// Gibt die erstbeste NodeConnection von from nach to zurück /// (es sollte eigentlich immer nur eine existieren, das wird aber nicht weiter geprüft) /// </summary> /// <param name="from">LineNode von dem die NodeConnection ausgeht</param> /// <param name="to">LineNode zu der die NodeConnection hingehet</param> /// <returns>NodeConnection, welche von from nach to läuft oder null, falls keine solche existiert</returns> public NodeConnection GetNodeConnection(LineNode from, LineNode to) { foreach (NodeConnection nc in this.connections) { if ((nc.startNode == from) && (nc.endNode == to)) { return nc; } } return null; }
/// <summary> /// legt einen neuen LinkedLineNode an /// </summary> /// <param name="node">der untersuchte Knoten</param> /// <param name="parent">Vorgängerknoten</param> /// <param name="lineChangeNeeded">Flag, ob ein Spurwechsel zum erreichen dieses Knotens nötig ist</param> public LinkedLineNode(LineNode node, LineNode.LinkedLineNode parent, bool lineChangeNeeded) { this.node = node; this.parent = parent; this.lineChangeNeeded = lineChangeNeeded; }
/// <summary> /// meldet den LineNode ln bei diesem TrafficLight an, sodass es weiß das es diesem zugeordnet ist /// </summary> /// <param name="ln">anzumeldender LineNode</param> public void AddAssignedLineNode(LineNode ln) { _assignedNodes.Add(ln); ln.tLight = this; }
/// <summary> /// sucht die NodeConnection zum LineNode lineNode heraus /// </summary> /// <param name="lineNode">zu suchender LineNode</param> /// <returns>erstbeste NodeConnection in nextConnections mit (nc.endNode == lineNode) oder null</returns> public NodeConnection GetNodeConnectionTo(LineNode lineNode) { foreach (NodeConnection lc in _nextConnections) { if (lc.endNode == lineNode) { return lc; } } return null; }
/// <summary> /// fügt einen LineNode hinzu /// </summary> /// <param name="ln">der hinzuzufügende LineNode</param> public void AddLineNode(LineNode ln) { nodes.Add(ln); InvalidateNodeBounds(); }
/// <summary> /// Aktualisiert sämtliche NodeConnections, welche von nodeToUpdate ausgehen /// </summary> /// <param name="nodeToUpdate">LineNode dessen ausgehende NodeConencitons aktualisiert werden sollen</param> public void UpdateOutgoingNodeConnections(LineNode nodeToUpdate) { foreach (NodeConnection nc in nodeToUpdate.nextConnections) { UpdateNodeConnection(nc); } }
/// <summary> /// Aktualisiert sämtliche NodeConnections, welche mit nodeToUpdate verbunden sind /// </summary> /// <param name="nodeToUpdate">LineNode dessen NodeConencitons aktualisiert werden sollen</param> public void UpdateNodeConnections(LineNode nodeToUpdate) { UpdateIncomingNodeConnections(nodeToUpdate); UpdateOutgoingNodeConnections(nodeToUpdate); }
/// <summary> /// Aktualisiert sämtliche NodeConnections, welche in nodeToUpdate eingehen /// </summary> /// <param name="nodeToUpdate">LineNode dessen eingehende NodeConencitons aktualisiert werden sollen</param> public void UpdateIncomingNodeConnections(LineNode nodeToUpdate) { foreach (NodeConnection nc in nodeToUpdate.prevConnections) { UpdateNodeConnection(nc); } }
/// <summary> /// Teilt die NodeConnection nc in zwei einzelne NodeConnections auf. /// Dabei wird in der Mitte natürlich auch ein neuer LineNode erstellt /// </summary> /// <param name="nc">aufzuteilende NodeConnection</param> public void SplitNodeConnection(NodeConnection nc) { LineNode startNode = nc.startNode; LineNode endNode = nc.endNode; // Mittelknoten erstellen LineNode middleNode = new LineNode(nc.lineSegment.subdividedFirst.p3, nc.startNode.networkLayer, false); middleNode.inSlopeAbs = nc.lineSegment.subdividedFirst.p2; middleNode.outSlopeAbs = nc.lineSegment.subdividedSecond.p1; nodes.Add(middleNode); // Anfangs- und Endknoten bearbeiten startNode.outSlopeAbs = nc.lineSegment.subdividedFirst.p1; endNode.inSlopeAbs = nc.lineSegment.subdividedSecond.p2; // Alte Connections lösen Disconnect(startNode, endNode); // Neue Connections bauen Connect(startNode, middleNode, nc.priority, nc.targetVelocity, nc.carsAllowed, nc.busAllowed, nc.tramAllowed, nc.enableIncomingLineChange, nc.enableOutgoingLineChange); Connect(middleNode, endNode, nc.priority, nc.targetVelocity, nc.carsAllowed, nc.busAllowed, nc.tramAllowed, nc.enableIncomingLineChange, nc.enableOutgoingLineChange); }
/// <summary> /// Berechnet den kürzesten Weg zum targetNode und speichert diesen als Stack in WayToGo /// Implementierung des A*-Algorithmus' frei nach Wikipedia :) /// </summary> /// <param name="startNode">Startknoten von dem aus der kürzeste Weg berechnet werden soll</param> /// <param name="targetNodes">Liste von Zielknoten zu einem von denen der kürzeste Weg berechnet werden soll</param> /// <param name="vehicleType">Vehicle type</param> public static Routing CalculateShortestConenction(LineNode startNode, List <LineNode> targetNodes, Vehicle.IVehicle.VehicleTypes vehicleType) { PriorityQueue <LineNode.LinkedLineNode, double> openlist = new PriorityQueue <LineNode.LinkedLineNode, double>(); Stack <LineNode.LinkedLineNode> closedlist = new Stack <LineNode.LinkedLineNode>(); Routing toReturn = new Routing(); // Initialisierung der Open List, die Closed List ist noch leer // (die Priorität bzw. der f Wert des Startknotens ist unerheblich) openlist.Enqueue(new LineNode.LinkedLineNode(startNode, null, false), 0); // diese Schleife wird durchlaufen bis entweder // - die optimale Lösung gefunden wurde oder // - feststeht, dass keine Lösung existiert do { // Knoten mit dem geringsten (in diesem Fall größten) f Wert aus der Open List entfernen PriorityQueueItem <LineNode.LinkedLineNode, double> currentNode = openlist.Dequeue(); // wurde das Ziel gefunden? if (targetNodes.Contains(currentNode.Value.node)) { // nun noch die closedList in eine Routing umwandeln closedlist.Push(currentNode.Value); LineNode.LinkedLineNode endnode = closedlist.Pop(); LineNode.LinkedLineNode startnode = endnode.parent; while (startnode != null) { // einfacher/direkter Weg über eine NodeConnection if (!endnode.lineChangeNeeded) { toReturn.Push(new RouteSegment(startnode.node.GetNodeConnectionTo(endnode.node), endnode.node, false, startnode.node.GetNodeConnectionTo(endnode.node).lineSegment.length)); } // Spurwechsel nötig else { NodeConnection formerConnection = startnode.parent.node.GetNodeConnectionTo(startnode.node); double length = formerConnection.GetLengthToLineNodeViaLineChange(endnode.node) + Constants.lineChangePenalty; // Anfangs-/ oder Endknoten des Spurwechsels ist eine Ampel => Kosten-Penalty, da hier verstärktes Verkehrsaufkommen zu erwarten ist if ((endnode.node.tLight != null) || (startnode.node.tLight != null)) { length += Constants.lineChangeBeforeTrafficLightPenalty; } toReturn.Push(new RouteSegment(formerConnection, endnode.node, true, length)); // TODO: Erklären: hier wird irgendwas doppelt gemacht - ich meine mich zu Erinnern, // das das so soll, aber nicht warum. Bitte beizeiten analysieren und erklären endnode = startnode; startnode = startnode.parent; } endnode = startnode; startnode = startnode.parent; } return(toReturn); } #region Nachfolgeknoten auf die Open List setzen // Nachfolgeknoten auf die Open List setzen // überprüft alle Nachfolgeknoten und fügt sie der Open List hinzu, wenn entweder // - der Nachfolgeknoten zum ersten Mal gefunden wird oder // - ein besserer Weg zu diesem Knoten gefunden wird #region nächste LineNodes ohne Spurwechsel untersuchen foreach (NodeConnection nc in currentNode.Value.node.nextConnections) { // prüfen, ob ich auf diesem NodeConnection überhaupt fahren darf if (!nc.CheckForSuitability(vehicleType)) { continue; } LineNode.LinkedLineNode successor = new LineNode.LinkedLineNode(nc.endNode, null, false); bool nodeInClosedList = false; foreach (LineNode.LinkedLineNode lln in closedlist) { if (lln.node == successor.node) { nodeInClosedList = true; continue; } } // wenn der Nachfolgeknoten bereits auf der Closed List ist - tue nichts if (!nodeInClosedList) { NodeConnection theConnection = currentNode.Value.node.GetNodeConnectionTo(successor.node); // f Wert für den neuen Weg berechnen: g Wert des Vorgängers plus die Kosten der // gerade benutzten Kante plus die geschätzten Kosten von Nachfolger bis Ziel double f = currentNode.Value.length // exakte Länge des bisher zurückgelegten Weges + theConnection.lineSegment.length; // exakte Länge des gerade untersuchten Segmentes if (currentNode.Value.countOfParents < 3) // Stau kostet extra, aber nur, wenn innerhalb { // der nächsten 2 Connections f += theConnection.vehicles.Count * Constants.vehicleOnRoutePenalty; } f += GetMinimumEuklidDistance(successor.node, targetNodes); // Minimumweg zum Ziel (Luftlinie) f *= 14 / theConnection.targetVelocity; f *= -1; // gucke, ob der Node schon in der Liste drin ist und wenn ja, dann evtl. rausschmeißen bool nodeInOpenlist = false; foreach (PriorityQueueItem <LineNode.LinkedLineNode, double> pqi in openlist) { if (pqi.Value.node == successor.node) { if (f <= pqi.Priority) { nodeInOpenlist = true; } else { openlist.Remove(pqi.Value); // erst entfernen } break; } } if (!nodeInOpenlist) { // Vorgängerzeiger setzen successor.parent = currentNode.Value; openlist.Enqueue(successor, f); // dann neu einfügen } } } #endregion #region nächste LineNodes mit Spurwechsel untersuchen if (currentNode.Value.parent != null) { NodeConnection currentConnection = currentNode.Value.parent.node.GetNodeConnectionTo(currentNode.Value.node); if (currentConnection != null) { foreach (LineNode ln in currentConnection.viaLineChangeReachableNodes) { // prüfen, ob ich diesen LineNode überhaupt anfahren darf if (!CheckLineNodeForIncomingSuitability(ln, vehicleType)) { continue; } // neuen LinkedLineNode erstellen LineNode.LinkedLineNode successor = new LineNode.LinkedLineNode(ln, null, true); bool nodeInClosedList = false; foreach (LineNode.LinkedLineNode lln in closedlist) { if (lln.node == successor.node) { nodeInClosedList = true; break; } } // wenn der Nachfolgeknoten bereits auf der Closed List ist - tue nichts if (!nodeInClosedList) { // passendes LineChangeInterval finden NodeConnection.LineChangeInterval lci; currentConnection.lineChangeIntervals.TryGetValue(ln.hashcode, out lci); if (lci.length < Constants.minimumLineChangeLength) { break; } // f-Wert für den neuen Weg berechnen: g Wert des Vorgängers plus die Kosten der // gerade benutzten Kante plus die geschätzten Kosten von Nachfolger bis Ziel double f = currentNode.Value.parent.length; // exakte Länge des bisher zurückgelegten Weges f += currentConnection.GetLengthToLineNodeViaLineChange(successor.node); // Kostenanteil, für den Spurwechsel dazuaddieren f += (lci.length < 2 * Constants.minimumLineChangeLength) ? 2 * Constants.lineChangePenalty : Constants.lineChangePenalty; // Anfangs-/ oder Endknoten des Spurwechsels ist eine Ampel => Kosten-Penalty, da hier verstärktes Verkehrsaufkommen zu erwarten ist if ((lci.targetNode.tLight != null) || (currentConnection.startNode.tLight != null)) { f += Constants.lineChangeBeforeTrafficLightPenalty; } f += GetMinimumEuklidDistance(successor.node, targetNodes); // Minimumweg zum Ziel (Luftlinie) f *= -1; // gucke, ob der Node schon in der Liste drin ist und wenn ja, dann evtl. rausschmeißen bool nodeInOpenlist = false; foreach (PriorityQueueItem <LineNode.LinkedLineNode, double> pqi in openlist) { if (pqi.Value.node == successor.node) { if (f <= pqi.Priority) { nodeInOpenlist = true; } else { openlist.Remove(pqi.Value); // erst entfernen } break; } } if (!nodeInOpenlist) { // Vorgängerzeiger setzen successor.parent = currentNode.Value; openlist.Enqueue(successor, f); // dann neu einfügen } } } } } #endregion #endregion // der aktuelle Knoten ist nun abschließend untersucht closedlist.Push(currentNode.Value); }while (openlist.Count != 0); // Es wurde kein Weg gefunden - dann lassen wir das Auto sich selbst zerstören: return(toReturn); }
/// <summary> /// berechnet die Bogenlängenentfernung vom Beginn dieser NodeConnection zum LineNode ln, wobei der erstbeste LineChangePoint zum wechseln benutzt wird. /// </summary> /// <param name="ln">Zielknoten</param> /// <returns></returns> public double GetLengthToLineNodeViaLineChange(LineNode ln) { foreach (LineChangePoint lcp in _lineChangePoints) { if (lcp.target.nc.endNode == ln) { return lcp.start.arcPosition + lcp.length + lcp.target.nc.lineSegment.length - lcp.target.arcPosition; } } return Double.PositiveInfinity; }
/// <summary> /// Stellt die Referenzen auf LineNodes wieder her /// (auszuführen nach XML-Deserialisierung) /// </summary> /// <param name="saveVersion">Version der gespeicherten Datei</param> /// <param name="nodesList">Liste der bereits wiederhergestellten LineNodes</param> public void RecoverFromLoad(int saveVersion, List<LineNode> nodesList) { // Workaround um falsche Wunschgeschwindigkeiten aus alten Dateien zu korrigieren if (saveVersion < 1) { m_wunschgeschwindigkeit *= 2; } foreach (LineNode ln in nodesList) { if (startNodeHashes.Contains(ln.GetHashCode())) startNodes.Add(ln); else if (endNodeHashes.Contains(ln.GetHashCode())) endNodes.Add(ln); // dies hier ist nur zur Abwärtskompatibilität von alten Speicherständen: if (ln.GetHashCode() == startNodeHash) startNode = ln; else if (ln.GetHashCode() == endNodeHash) endNode = ln; } // dies hier ist nur zur Abwärtskompatibilität von alten Speicherständen: if (startNode != null && !startNodes.Contains(startNode)) startNodes.Add(startNode); if (endNode != null && !endNodes.Contains(endNode)) endNodes.Add(endNode); }
/// <summary> /// stellt eine NodeConnection von from nach to her /// </summary> /// <param name="from">LineNode von dem die NodeConnection ausgehen soll</param> /// <param name="to">LineNode zu der die NodeConnection hingehen soll</param> /// <param name="priority">Priorität der Linie</param> /// <param name="targetVelocity">Target velocity on the NodeConnection</param> /// <param name="carsAllowed">Flag, ob Autos auf dieser NodeConnection erlaubt sind</param> /// <param name="busAllowed">Flag, ob Busse auf dieser NodeConnection erlaubt sind</param> /// <param name="tramAllowed">Flag, ob Straßenbahnen auf dieser NodeConnection erlaubt sind</param> /// <param name="enableIncomingLineChange">Flag, ob eingehende Spurwechsel erlaubt sind</param> /// <param name="enableOutgoingLineChange">Flag, ob ausgehende Spurchwechsel erlaubt sind</param> public void Connect(LineNode from, LineNode to, int priority, double targetVelocity, bool carsAllowed, bool busAllowed, bool tramAllowed, bool enableIncomingLineChange, bool enableOutgoingLineChange) { NodeConnection nc = new NodeConnection(from, to, null, priority, targetVelocity, carsAllowed, busAllowed, tramAllowed, enableIncomingLineChange, enableOutgoingLineChange); TellNodesTheirConnection(nc); UpdateNodeConnection(nc); ResetAverageVelocities(nc); connections.Add(nc); }
/// <summary> /// Löscht einen LineNode und alle damit verbundenen NodeConnections /// </summary> /// <param name="ln">zu löschender LineNode</param> public void DeleteLineNode(LineNode ln) { // ausgehende NodeConnections löschen while (ln.nextConnections.Count > 0) { Disconnect(ln, ln.nextConnections[0].endNode); } // eingehende NodeConnections löschen while (ln.prevConnections.Count > 0) { Disconnect(ln.prevConnections[0].startNode, ln); } nodes.Remove(ln); InvalidateNodeBounds(); }
/// <summary> /// meldet den LineNode ln bei diesem TrafficLight wieder ab, sodass es weiß, dass es diesem nicht mehr zugeordnet ist /// </summary> /// <param name="ln">abzumeldender LineNode</param> /// <returns>true, falls der Abmeldevorgang erfolgreich, sonst false</returns> public bool RemoveAssignedLineNode(LineNode ln) { if (ln != null) { ln.tLight = null; return _assignedNodes.Remove(ln); } return false; }
/// <summary> /// kappt die sämtliche bestehende NodeConnection zwischen from und to und sagt den Nodes auch Bescheid, dass diese nicht mehr bestehen /// </summary> /// <param name="from">LineNode von dem die NodeConnection ausgeht</param> /// <param name="to">LineNode zu der die NodeConnection hingehet</param> public void Disconnect(LineNode from, LineNode to) { NodeConnection nc; while ((nc = GetNodeConnection(from, to)) != null) { // Intersections löschen while (nc.intersections.Count > 0) { DestroyIntersection(nc.intersections.First.Value); } // LineChangePoints löschen, so sie denn exisitieren RemoveLineChangePoints(nc, true, true); // Connections lösen und löschen from.nextConnections.Remove(nc); to.prevConnections.Remove(nc); connections.Remove(nc); } }