Network.java

  1. package org.opentrafficsim.core.network;

  2. import java.awt.geom.Rectangle2D;
  3. import java.io.Serializable;
  4. import java.util.ArrayList;
  5. import java.util.Collections;
  6. import java.util.LinkedHashMap;
  7. import java.util.LinkedHashSet;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.Map.Entry;
  11. import java.util.Set;

  12. import org.djutils.draw.bounds.Bounds2d;
  13. import org.djutils.event.EventProducer;
  14. import org.djutils.event.EventType;
  15. import org.djutils.event.LocalEventProducer;
  16. import org.djutils.immutablecollections.Immutable;
  17. import org.djutils.immutablecollections.ImmutableHashMap;
  18. import org.djutils.immutablecollections.ImmutableMap;
  19. import org.djutils.logger.CategoryLogger;
  20. import org.djutils.metadata.MetaData;
  21. import org.djutils.metadata.ObjectDescriptor;
  22. import org.djutils.multikeymap.MultiKeyMap;
  23. import org.jgrapht.GraphPath;
  24. import org.jgrapht.alg.shortestpath.AStarShortestPath;
  25. import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
  26. import org.jgrapht.graph.SimpleDirectedWeightedGraph;
  27. import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
  28. import org.opentrafficsim.core.gtu.Gtu;
  29. import org.opentrafficsim.core.gtu.GtuType;
  30. import org.opentrafficsim.core.network.route.Route;
  31. import org.opentrafficsim.core.object.LocatedObject;
  32. import org.opentrafficsim.core.object.NonLocatedObject;
  33. import org.opentrafficsim.core.perception.PerceivableContext;

  34. /**
  35.  * A Network consists of a set of links. Each link has, in its turn, a start node and an end node.
  36.  * <p>
  37.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  38.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  39.  * <p>
  40.  * $LastChangedDate$, @version $Revision$, by $Author$, initial version Jul 22, 2015 <br>
  41.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  42.  * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  43.  * @author <a href="https://www.citg.tudelft.nl">Guus Tamminga</a>
  44.  */
  45. public class Network extends LocalEventProducer implements PerceivableContext, Serializable, EventProducer
  46. {
  47.     /** */
  48.     private static final long serialVersionUID = 20150722;

  49.     /** Id of this network. */
  50.     private final String id;

  51.     /** Map of Nodes. */
  52.     private Map<String, Node> nodeMap = Collections.synchronizedMap(new LinkedHashMap<>());

  53.     /** Map of Links. */
  54.     private Map<String, Link> linkMap = Collections.synchronizedMap(new LinkedHashMap<>());

  55.     /** Map of LocatedObject. */
  56.     private Map<String, LocatedObject> objectMap = Collections.synchronizedMap(new LinkedHashMap<>());

  57.     /** Map of NonLocatedObjects. */
  58.     private Map<String, NonLocatedObject> nonLocatedObjectMap = Collections.synchronizedMap(new LinkedHashMap<>());

  59.     /** Map of Routes. */
  60.     private Map<GtuType, Map<String, Route>> routeMap = Collections.synchronizedMap(new LinkedHashMap<>());

  61.     /** Graphs to calculate shortest paths per GtuType and LinkWeight. */
  62.     private MultiKeyMap<SimpleDirectedWeightedGraph<Node, Link>> linkGraphs =
  63.             new MultiKeyMap<>(GtuType.class, LinkWeight.class);

  64.     /** GTUs registered in this network. */
  65.     private Map<String, Gtu> gtuMap = Collections.synchronizedMap(new LinkedHashMap<>());

  66.     /** The DSOL simulator engine. */
  67.     private final OtsSimulatorInterface simulator;

  68.     /**
  69.      * Construction of an empty network.
  70.      * @param id the network id.
  71.      * @param simulator the DSOL simulator engine
  72.      */
  73.     public Network(final String id, final OtsSimulatorInterface simulator)
  74.     {
  75.         this.id = id;
  76.         this.simulator = simulator;
  77.     }

  78.     @Override
  79.     public final String getId()
  80.     {
  81.         return this.id;
  82.     }

  83.     /**
  84.      * Return the simulator.
  85.      * @return the simulator
  86.      */
  87.     public OtsSimulatorInterface getSimulator()
  88.     {
  89.         return this.simulator;
  90.     }

  91.     /***************************************************************************************/
  92.     /**************************************** NODES ****************************************/
  93.     /***************************************************************************************/

  94.     /**
  95.      * Provide an immutable map of node ids to nodes in the network.
  96.      * @return an immutable map of nodes.
  97.      */
  98.     public final ImmutableMap<String, Node> getNodeMap()
  99.     {
  100.         return new ImmutableHashMap<>(this.nodeMap, Immutable.WRAP);
  101.     }

  102.     /**
  103.      * @return only to be used in the 'network' package for cloning.
  104.      */
  105.     final Map<String, Node> getRawNodeMap()
  106.     {
  107.         return this.nodeMap;
  108.     }

  109.     /**
  110.      * Register a node in the network.
  111.      * @param node the node to add to the network.
  112.      * @throws NetworkException if node already exists in the network, or if name of the node is not unique.
  113.      */
  114.     public final void addNode(final Node node) throws NetworkException
  115.     {
  116.         if (containsNode(node))
  117.         {
  118.             throw new NetworkException("Node " + node + " already registered in network " + this.id);
  119.         }
  120.         this.nodeMap.put(node.getId(), node);
  121.         fireTimedEvent(Network.NODE_ADD_EVENT, node.getId(), getSimulator().getSimulatorTime());
  122.     }

  123.     /**
  124.      * Unregister a node from the network.
  125.      * @param node the node to remove from the network.
  126.      * @throws NetworkException if node does not exist in the network.
  127.      */
  128.     public final void removeNode(final Node node) throws NetworkException
  129.     {
  130.         if (!containsNode(node))
  131.         {
  132.             throw new NetworkException("Node " + node + " not registered in network " + this.id);
  133.         }
  134.         fireTimedEvent(Network.NODE_REMOVE_EVENT, node.getId(), getSimulator().getSimulatorTime());
  135.         this.nodeMap.remove(node.getId());
  136.     }

  137.     /**
  138.      * Test whether a node is present in the network.
  139.      * @param node the node to search for in the network.
  140.      * @return whether the node is in this network
  141.      */
  142.     public final boolean containsNode(final Node node)
  143.     {
  144.         // System.out.println(node);
  145.         return this.nodeMap.keySet().contains(node.getId());
  146.     }

  147.     /**
  148.      * Test whether a node with a given id is present in the network.
  149.      * @param nodeId the id of the node to search for in the network.
  150.      * @return whether the node is in this network
  151.      */
  152.     public final boolean containsNode(final String nodeId)
  153.     {
  154.         return this.nodeMap.keySet().contains(nodeId);
  155.     }

  156.     /**
  157.      * Retrieve a node with a given id from the network, or null if the id cannot be found.
  158.      * @param nodeId the id of the node to search for in the network.
  159.      * @return the node or null if not present
  160.      */
  161.     public final Node getNode(final String nodeId)
  162.     {
  163.         return this.nodeMap.get(nodeId);
  164.     }

  165.     /**
  166.      * Return a list of Centroid nodes that have incoming connectors without corresponding outgoing connectors to the same node
  167.      * or vice versa (which can be fully okay, especially when the lanes are a dead end, or when lanes / links only go in a
  168.      * single direction).
  169.      * @param gtuType the GTU type for which to check the connectors
  170.      * @return a list of Centroid nodes that have incoming connectors without corresponding outgoing connectors to the same node
  171.      *         or vice versa.
  172.      */
  173.     public List<Node> getUnbalancedCentroids(final GtuType gtuType)
  174.     {
  175.         List<Node> centroidList = new ArrayList<>();
  176.         for (Node node : getRawNodeMap().values())
  177.         {
  178.             if (node.isCentroid())
  179.             {
  180.                 boolean in = false;
  181.                 boolean out = false;
  182.                 for (Link link : node.getLinks())
  183.                 {
  184.                     if (link.getEndNode().equals(node))
  185.                     {
  186.                         in = true;
  187.                     }
  188.                     else if (link.getStartNode().equals(node))
  189.                     {
  190.                         out = true;
  191.                     }
  192.                 }
  193.                 if ((in && !out) || (out && !in))
  194.                 {
  195.                     centroidList.add(node);
  196.                 }
  197.             }
  198.         }
  199.         return centroidList;
  200.     }

  201.     /***************************************************************************************/
  202.     /**************************************** LINKS ****************************************/
  203.     /***************************************************************************************/

  204.     /**
  205.      * Provide an immutable map of link ids to links in the network.
  206.      * @return the an immutable map of links.
  207.      */
  208.     public final ImmutableMap<String, Link> getLinkMap()
  209.     {
  210.         return new ImmutableHashMap<>(this.linkMap, Immutable.WRAP);
  211.     }

  212.     /**
  213.      * @return only to be used in the 'network' package for cloning.
  214.      */
  215.     final Map<String, Link> getRawLinkMap()
  216.     {
  217.         return this.linkMap;
  218.     }

  219.     /**
  220.      * Register a link in the network.
  221.      * @param link the link to add to the network.
  222.      * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
  223.      *             or the end node of the link are not registered in the network.
  224.      */
  225.     public final void addLink(final Link link) throws NetworkException
  226.     {
  227.         if (containsLink(link))
  228.         {
  229.             throw new NetworkException("Link " + link + " already registered in network " + this.id);
  230.         }
  231.         if (this.linkMap.keySet().contains(link.getId()))
  232.         {
  233.             throw new NetworkException("Link with name " + link.getId() + " already registered in network " + this.id);
  234.         }
  235.         if (!containsNode(link.getStartNode()) || !containsNode(link.getEndNode()))
  236.         {
  237.             throw new NetworkException(
  238.                     "Start node or end node of Link " + link.getId() + " not registered in network " + this.id);
  239.         }
  240.         this.linkMap.put(link.getId(), link);
  241.         fireTimedEvent(Network.LINK_ADD_EVENT, link.getId(), getSimulator().getSimulatorTime());
  242.     }

  243.     /**
  244.      * Unregister a link from the network.
  245.      * @param link the link to remove from the network.
  246.      * @throws NetworkException if link does not exist in the network.
  247.      */
  248.     public final void removeLink(final Link link) throws NetworkException
  249.     {
  250.         if (!containsLink(link))
  251.         {
  252.             throw new NetworkException("Link " + link + " not registered in network " + this.id);
  253.         }
  254.         fireTimedEvent(Network.LINK_REMOVE_EVENT, link.getId(), getSimulator().getSimulatorTime());
  255.         this.linkMap.remove(link.getId());
  256.     }

  257.     /**
  258.      * Find a link between node1 and node2 and return it if it exists in the network. If not, return null.
  259.      * @param node1 first node
  260.      * @param node2 second node
  261.      * @return the link between node1 and node2 in the network or null if it does not exist.
  262.      */
  263.     public final Link getLink(final Node node1, final Node node2)
  264.     {
  265.         for (Link link : this.linkMap.values())
  266.         {
  267.             if (link.getStartNode().equals(node1) && link.getEndNode().equals(node2))
  268.             {
  269.                 return link;
  270.             }
  271.         }
  272.         return null;
  273.     }

  274.     /**
  275.      * Test whether a link is present in the network.
  276.      * @param link the link to search for in the network.
  277.      * @return whether the link is in this network
  278.      */
  279.     public final boolean containsLink(final Link link)
  280.     {
  281.         return this.linkMap.keySet().contains(link.getId());
  282.     }

  283.     /**
  284.      * Test whether a link with a given id is present in the network.
  285.      * @param linkId the id of the link to search for in the network.
  286.      * @return whether the link is in this network
  287.      */
  288.     public final boolean containsLink(final String linkId)
  289.     {
  290.         return this.linkMap.keySet().contains(linkId);
  291.     }

  292.     /**
  293.      * Find a link between node1 and node2 and return it if it exists in the network. If not, return null.
  294.      * @param nodeId1 id of the first node
  295.      * @param nodeId2 id of the second node
  296.      * @return the link between node1 and node2 in the network or null if it does not exist.
  297.      * @throws NetworkException if the node(s) cannot be found by their id
  298.      */
  299.     public final Link getLink(final String nodeId1, final String nodeId2) throws NetworkException
  300.     {
  301.         if (!containsNode(nodeId1))
  302.         {
  303.             throw new NetworkException("Node " + nodeId1 + " not in network " + this.id);
  304.         }
  305.         if (!containsNode(nodeId2))
  306.         {
  307.             throw new NetworkException("Node " + nodeId2 + " not in network " + this.id);
  308.         }
  309.         return getLink(getNode(nodeId1), getNode(nodeId2));
  310.     }

  311.     /**
  312.      * Retrieve a node with a given id from the network, or null if the id cannot be found.
  313.      * @param linkId the id of the link to search for in the network.
  314.      * @return the link or null if not present
  315.      */
  316.     public final Link getLink(final String linkId)
  317.     {
  318.         return this.linkMap.get(linkId);
  319.     }

  320.     /***************************************************************************************/
  321.     /************************ OBJECT INTERFACE IMPLEMENTING OBJECTS ************************/
  322.     /***************************************************************************************/

  323.     /**
  324.      * Return an immutable map of all ObjectInterface implementing objects in the Network.
  325.      * @return the immutable map of all ObjectInterface implementing objects in the Network
  326.      */
  327.     public final ImmutableMap<String, LocatedObject> getObjectMap()
  328.     {
  329.         return new ImmutableHashMap<>(this.objectMap, Immutable.WRAP);
  330.     }

  331.     /**
  332.      * @return only to be used in the 'network' package for cloning.
  333.      */
  334.     final Map<String, LocatedObject> getRawObjectMap()
  335.     {
  336.         return this.objectMap;
  337.     }

  338.     /**
  339.      * Return an immutable map of all ObjectInterface implementing objects in the network that are of type objectType, or any
  340.      * sub type thereof.
  341.      * @param objectType the (sub-)type of ObjectInterface that the returned map is reduced to
  342.      * @param <T> type of object
  343.      * @return the immutable map of all ObjectInterface implementing objects in the Network that are of the type objectType, or
  344.      *         any sub type thereof
  345.      */
  346.     @SuppressWarnings("unchecked")
  347.     public final <T extends LocatedObject> ImmutableMap<String, T> getObjectMap(final Class<T> objectType)
  348.     {
  349.         Map<String, T> result = new LinkedHashMap<>();
  350.         for (String key : this.objectMap.keySet())
  351.         {
  352.             LocatedObject o = this.objectMap.get(key);
  353.             if (objectType.isInstance(o))
  354.             {
  355.                 result.put(key, (T) o);
  356.             }
  357.         }
  358.         return new ImmutableHashMap<>(result, Immutable.WRAP);
  359.     }

  360.     /**
  361.      * Return object of given type with given id.
  362.      * @param objectType object type class
  363.      * @param objectId id of object
  364.      * @param <T> object type
  365.      * @return object of given type with given id, {@code null} if no such object
  366.      */
  367.     @SuppressWarnings("unchecked")
  368.     public final <T extends LocatedObject> T getObject(final Class<T> objectType, final String objectId)
  369.     {
  370.         for (Entry<String, LocatedObject> entry : this.objectMap.entrySet())
  371.         {
  372.             if (entry.getKey().equals(objectId) && objectType.isInstance(entry.getValue()))
  373.             {
  374.                 return (T) entry.getValue();
  375.             }
  376.         }
  377.         return null;
  378.     }

  379.     /**
  380.      * Add an ObjectInterface implementing object to the Network.
  381.      * @param object the object that implements ObjectInterface
  382.      * @throws NetworkException if link already exists in the network, if name of the object is not unique.
  383.      */
  384.     public final void addObject(final LocatedObject object) throws NetworkException
  385.     {
  386.         if (containsObject(object))
  387.         {
  388.             throw new NetworkException("Object " + object + " already registered in network " + this.id);
  389.         }
  390.         if (containsObject(object.getFullId()))
  391.         {
  392.             throw new NetworkException("Object with name " + object.getFullId() + " already registered in network " + this.id);
  393.         }
  394.         this.objectMap.put(object.getFullId(), object);
  395.         fireTimedEvent(Network.OBJECT_ADD_EVENT, object.getFullId(), getSimulator().getSimulatorTime());
  396.     }

  397.     /**
  398.      * Remove an ObjectInterface implementing object form the Network.
  399.      * @param object the object that implements ObjectInterface
  400.      * @throws NetworkException if the object does not exist in the network.
  401.      */
  402.     public final void removeObject(final LocatedObject object) throws NetworkException
  403.     {
  404.         if (!containsObject(object))
  405.         {
  406.             throw new NetworkException("Object " + object + " not registered in network " + this.id);
  407.         }
  408.         fireTimedEvent(Network.OBJECT_REMOVE_EVENT, object.getFullId(), getSimulator().getSimulatorTime());
  409.         this.objectMap.remove(object.getFullId());
  410.     }

  411.     /**
  412.      * Test whether the object is present in the Network.
  413.      * @param object the object that is tested for presence
  414.      * @return whether the object is present in the Network
  415.      */
  416.     public final boolean containsObject(final LocatedObject object)
  417.     {
  418.         return this.objectMap.containsKey(object.getFullId());
  419.     }

  420.     /**
  421.      * Test whether an object with the given id is present in the Network.
  422.      * <p>
  423.      * Note that the objectId should be the <b>fullId</b> of the object, including any additions such as lane ids, link ids,
  424.      * etc.
  425.      * @param objectId the id that is tested for presence
  426.      * @return whether an object with the given id is present in the Network
  427.      */
  428.     public final boolean containsObject(final String objectId)
  429.     {
  430.         return this.objectMap.containsKey(objectId);
  431.     }

  432.     /***************************************************************************************/
  433.     /******************************** NON LOCATED OBJECTS **********************************/
  434.     /***************************************************************************************/

  435.     /**
  436.      * Return an immutable map of all NonLocatedObject implementing objects in the Network.
  437.      * @return the immutable map of all NonLocatedObject implementing objects in the Network
  438.      */
  439.     public final ImmutableMap<String, NonLocatedObject> getNonLocatedObjectMap()
  440.     {
  441.         return new ImmutableHashMap<>(this.nonLocatedObjectMap, Immutable.WRAP);
  442.     }

  443.     /**
  444.      * @return only to be used in the 'network' package for cloning.
  445.      */
  446.     final Map<String, NonLocatedObject> getRawNonLocatedObjectMap()
  447.     {
  448.         return this.nonLocatedObjectMap;
  449.     }

  450.     /**
  451.      * Return an immutable map of all NonLocatedObject implementing objects in the network that are of type objectType, or any
  452.      * sub type thereof.
  453.      * @param objectType the (sub-)type of NonLocatedObject that the returned map is reduced to
  454.      * @return the immutable map of all NonLocatedObject implementing objects in the Network that are of the type objectType, or
  455.      *         any sub type thereof
  456.      */
  457.     public final ImmutableMap<String, NonLocatedObject> getNonLocatedObjectMap(final Class<NonLocatedObject> objectType)
  458.     {
  459.         Map<String, NonLocatedObject> result = new LinkedHashMap<>();
  460.         for (String key : this.objectMap.keySet())
  461.         {
  462.             NonLocatedObject o = this.nonLocatedObjectMap.get(key);
  463.             if (objectType.isInstance(o))
  464.             {
  465.                 result.put(key, o);
  466.             }
  467.         }
  468.         return new ImmutableHashMap<>(result, Immutable.WRAP);
  469.     }

  470.     /**
  471.      * Add a NonLocatedObject implementing object to the Network.
  472.      * @param object the object that implements ObjectInterface
  473.      * @throws NetworkException if link already exists in the network, if name of the object is not unique.
  474.      */
  475.     public final void addNonLocatedObject(final NonLocatedObject object) throws NetworkException
  476.     {
  477.         if (containsNonLocatedObject(object))
  478.         {
  479.             throw new NetworkException("NonLocatedObject " + object + " already registered in network " + this.id);
  480.         }
  481.         if (containsNonLocatedObject(object.getFullId()))
  482.         {
  483.             throw new NetworkException(
  484.                     "NonLocatedObject with name " + object.getFullId() + " already registered in network " + this.id);
  485.         }
  486.         this.nonLocatedObjectMap.put(object.getFullId(), object);
  487.         fireTimedEvent(Network.NONLOCATED_OBJECT_ADD_EVENT, object.getFullId(), getSimulator().getSimulatorTime());
  488.     }

  489.     /**
  490.      * Remove a NonLocatedObject implementing object form the Network.
  491.      * @param object the object that implements ObjectInterface
  492.      * @throws NetworkException if the object does not exist in the network.
  493.      */
  494.     public final void removeNonLocatedObject(final NonLocatedObject object) throws NetworkException
  495.     {
  496.         if (!containsNonLocatedObject(object))
  497.         {
  498.             throw new NetworkException("NonLocatedObject " + object + " not registered in network " + this.id);
  499.         }
  500.         fireTimedEvent(Network.NONLOCATED_OBJECT_REMOVE_EVENT, object.getFullId(), getSimulator().getSimulatorTime());
  501.         this.objectMap.remove(object.getFullId());
  502.     }

  503.     /**
  504.      * Test whether the NonLocatedObject is present in the Network.
  505.      * @param object the object that is tested for presence
  506.      * @return whether the invisible object is present in the Network
  507.      */
  508.     public final boolean containsNonLocatedObject(final NonLocatedObject object)
  509.     {
  510.         return this.nonLocatedObjectMap.containsKey(object.getFullId());
  511.     }

  512.     /**
  513.      * Test whether an NonLocatedObject object with the given id is present in the Network.
  514.      * <p>
  515.      * Note that the objectId should be the <b>fullId</b> of the object, including any additions such as lane ids, link ids,
  516.      * etc.
  517.      * @param objectId the id that is tested for presence
  518.      * @return whether an invisible object with the given id is present in the Network
  519.      */
  520.     public final boolean containsNonLocatedObject(final String objectId)
  521.     {
  522.         return this.nonLocatedObjectMap.containsKey(objectId);
  523.     }

  524.     /***************************************************************************************/
  525.     /*************************************** ROUTES ****************************************/
  526.     /***************************************************************************************/

  527.     /**
  528.      * Return an immutable map of routes that exist in the network for the GtuType.
  529.      * @param gtuType the GtuType for which to retrieve the defined routes
  530.      * @return an immutable map of routes in the network for the given GtuType, or an empty Map if no routes are defined for the
  531.      *         given GtuType.
  532.      */
  533.     public final ImmutableMap<String, Route> getDefinedRouteMap(final GtuType gtuType)
  534.     {
  535.         Map<String, Route> routes = new LinkedHashMap<>();
  536.         if (this.routeMap.containsKey(gtuType))
  537.         {
  538.             routes.putAll(this.routeMap.get(gtuType));
  539.         }
  540.         return new ImmutableHashMap<>(routes, Immutable.WRAP);
  541.     }

  542.     /**
  543.      * Add a route to the network.
  544.      * @param gtuType the GtuType for which to add a route
  545.      * @param route the route to add to the network.
  546.      * @throws NetworkException if route already exists in the network, if name of the route is not unique, if one of the nodes
  547.      *             of the route are not registered in the network.
  548.      */
  549.     public final void addRoute(final GtuType gtuType, final Route route) throws NetworkException
  550.     {
  551.         if (containsRoute(gtuType, route))
  552.         {
  553.             throw new NetworkException(
  554.                     "Route " + route + " for GtuType " + gtuType + " already registered in network " + this.id);
  555.         }
  556.         if (this.routeMap.containsKey(gtuType) && this.routeMap.get(gtuType).keySet().contains(route.getId()))
  557.         {
  558.             throw new NetworkException("Route with name " + route.getId() + " for GtuType " + gtuType
  559.                     + " already registered in network " + this.id);
  560.         }
  561.         for (Node node : route.getNodes())
  562.         {
  563.             if (!containsNode(node))
  564.             {
  565.                 throw new NetworkException("Node " + node.getId() + " of route " + route.getId() + " for GtuType " + gtuType
  566.                         + " not registered in network " + this.id);
  567.             }
  568.         }
  569.         if (!this.routeMap.containsKey(gtuType))
  570.         {
  571.             this.routeMap.put(gtuType, new LinkedHashMap<String, Route>());
  572.         }
  573.         this.routeMap.get(gtuType).put(route.getId(), route);
  574.         fireTimedEvent(Network.ROUTE_ADD_EVENT, new Object[] {gtuType.getId(), route.getId()},
  575.                 getSimulator().getSimulatorTime());
  576.     }

  577.     /**
  578.      * Remove the route from the network, e.g. because of road maintenance.
  579.      * @param gtuType the GtuType for which to remove a route
  580.      * @param route the route to remove from the network.
  581.      * @throws NetworkException if route does not exist in the network.
  582.      */
  583.     public final void removeRoute(final GtuType gtuType, final Route route) throws NetworkException
  584.     {
  585.         if (!containsRoute(gtuType, route))
  586.         {
  587.             throw new NetworkException("Route " + route + " for GtuType " + gtuType + " not registered in network " + this.id);
  588.         }
  589.         fireTimedEvent(Network.ROUTE_REMOVE_EVENT, new Object[] {gtuType.getId(), route.getId()},
  590.                 getSimulator().getSimulatorTime());
  591.         this.routeMap.get(gtuType).remove(route.getId());
  592.     }

  593.     /**
  594.      * Determine whether the provided route exists in the network for the given GtuType.
  595.      * @param gtuType the GtuType for which to check whether the route exists
  596.      * @param route the route to check for
  597.      * @return whether the route exists in the network for the given GtuType
  598.      */
  599.     public final boolean containsRoute(final GtuType gtuType, final Route route)
  600.     {
  601.         if (this.routeMap.containsKey(gtuType))
  602.         {
  603.             return this.routeMap.get(gtuType).values().contains(route);
  604.         }
  605.         return false;
  606.     }

  607.     /**
  608.      * Determine whether a route with the given id exists in the network for the given GtuType.
  609.      * @param gtuType the GtuType for which to check whether the route exists
  610.      * @param routeId the id of the route to check for
  611.      * @return whether a route with the given id exists in the network for the given GtuType
  612.      */
  613.     public final boolean containsRoute(final GtuType gtuType, final String routeId)
  614.     {
  615.         if (this.routeMap.containsKey(gtuType))
  616.         {
  617.             return this.routeMap.get(gtuType).keySet().contains(routeId);
  618.         }
  619.         return false;
  620.     }

  621.     /**
  622.      * Returns the route with given id or {@code null} if no such route is available.
  623.      * @param routeId route id
  624.      * @return route with given id or {@code null} if no such route is available
  625.      */
  626.     public final Route getRoute(final String routeId)
  627.     {
  628.         for (GtuType gtuType : this.routeMap.keySet())
  629.         {
  630.             Route route = this.routeMap.get(gtuType).get(routeId);
  631.             if (route != null)
  632.             {
  633.                 return route;
  634.             }
  635.         }
  636.         return null;
  637.     }

  638.     /**
  639.      * Return the route with the given id in the network for the given GtuType, or null if it the route with the id does not
  640.      * exist.
  641.      * @param gtuType the GtuType for which to retrieve a route based on its id.
  642.      * @param routeId the route to search for in the network.
  643.      * @return the route or null if not present
  644.      */
  645.     public final Route getRoute(final GtuType gtuType, final String routeId)
  646.     {
  647.         if (this.routeMap.containsKey(gtuType))
  648.         {
  649.             return this.routeMap.get(gtuType).get(routeId);
  650.         }
  651.         return null;
  652.     }

  653.     /**
  654.      * Return the the shortest route between two nodes in the network, via a list of intermediate nodes. If no path exists from
  655.      * the start node to the end node via the intermediate nodes in the network, null is returned.
  656.      * @param gtuType the GtuType for which to retrieve the defined routes
  657.      * @param nodeFrom the start node.
  658.      * @param nodeTo the end node.
  659.      * @return if no route can be found, an empty set is returned.
  660.      */
  661.     public final Set<Route> getRoutesBetween(final GtuType gtuType, final Node nodeFrom, final Node nodeTo)
  662.     {
  663.         Set<Route> routes = new LinkedHashSet<>();
  664.         if (this.routeMap.containsKey(gtuType))
  665.         {
  666.             for (Route route : this.routeMap.get(gtuType).values())
  667.             {
  668.                 try
  669.                 {
  670.                     if (route.originNode().equals(nodeFrom) && route.destinationNode().equals(nodeTo))
  671.                     {
  672.                         routes.add(route);
  673.                     }
  674.                 }
  675.                 catch (NetworkException ne)
  676.                 {
  677.                     // thrown if no nodes exist in the route. Do not add the route in that case.
  678.                 }
  679.             }
  680.         }
  681.         return routes;
  682.     }

  683.     /**
  684.      * Calculate the shortest route between two nodes in the network. If no path exists from the start node to the end node in
  685.      * the network, null is returned. This method returns a CompleteRoute, which includes all nodes to get from start to end. In
  686.      * case the graph for the GtuType has not yet been built, this method will call the buildGraph method.
  687.      * @param gtuType the GtuType for which to calculate the shortest route
  688.      * @param nodeFrom the start node.
  689.      * @param nodeTo the end node.
  690.      * @return the shortest route from the start Node to the end Node in the network. If no path exists from the start node to
  691.      *         the end node in the network, null is returned.
  692.      * @throws NetworkException in case nodes cannot be added to the route, e.g. because they are not directly connected. This
  693.      *             can be the case when the links in the network have changed, but the graph has not been rebuilt.
  694.      */
  695.     public Route getShortestRouteBetween(final GtuType gtuType, final Node nodeFrom, final Node nodeTo) throws NetworkException
  696.     {
  697.         return getShortestRouteBetween(gtuType, nodeFrom, nodeTo, LinkWeight.LENGTH);
  698.     }

  699.     /**
  700.      * Builds a graph using the specified link weight.
  701.      * @param gtuType GTU type
  702.      * @param linkWeight link weight
  703.      * @return SimpleDirectedWeightedGraph graph
  704.      */
  705.     private SimpleDirectedWeightedGraph<Node, Link> buildGraph(final GtuType gtuType, final LinkWeight linkWeight)
  706.     {
  707.         // TODO: take connections into account, and possibly do node expansion to build the graph
  708.         SimpleDirectedWeightedGraph<Node, Link> graph = new SimpleDirectedWeightedGraph<>(Link.class);
  709.         for (Node node : this.nodeMap.values())
  710.         {
  711.             graph.addVertex(node);
  712.         }
  713.         for (Link link : this.linkMap.values())
  714.         {
  715.             // determine if the link is accessible for the GtuType , and in which direction(s)
  716.             graph.addEdge(link.getStartNode(), link.getEndNode(), link);
  717.             graph.setEdgeWeight(link, linkWeight.getWeight(link));
  718.         }
  719.         return graph;
  720.     }

  721.     /**
  722.      * Calculate the shortest route between two nodes in the network. If no path exists from the start node to the end node in
  723.      * the network, null is returned. This method returns a CompleteRoute, which includes all nodes to get from start to end.
  724.      * This method recalculates the graph.
  725.      * @param gtuType the GtuType for which to calculate the shortest route
  726.      * @param nodeFrom the start node.
  727.      * @param nodeTo the end node.
  728.      * @param linkWeight link weight.
  729.      * @return the shortest route from the start Node to the end Node in the network. If no path exists from the start node to
  730.      *         the end node in the network, null is returned.
  731.      * @throws NetworkException in case nodes cannot be added to the route, e.g. because they are not directly connected. This
  732.      *             can be the case when the links in the network have changed, but the graph has not been rebuilt.
  733.      */
  734.     public final Route getShortestRouteBetween(final GtuType gtuType, final Node nodeFrom, final Node nodeTo,
  735.             final LinkWeight linkWeight) throws NetworkException
  736.     {
  737.         return getShortestRouteBetween(gtuType, nodeFrom, nodeTo, new ArrayList<>(), linkWeight);
  738.     }

  739.     /**
  740.      * Calculate the shortest route between two nodes in the network, via a list of intermediate nodes. If no path exists from
  741.      * the start node to the end node via the intermediate nodes in the network, null is returned. This method returns a
  742.      * CompleteRoute, which includes all nodes to get from start to end. This method recalculates the graph.
  743.      * @param gtuType the GtuType for which to calculate the shortest route
  744.      * @param nodeFrom the start node.
  745.      * @param nodeTo the end node.
  746.      * @param nodesVia a number of nodes that the GTU has to pass between nodeFrom and nodeTo in the given order.
  747.      * @return the shortest route between two nodes in the network, via the intermediate nodes. If no path exists from the start
  748.      *         node to the end node via the intermediate nodes in the network, null is returned.
  749.      * @throws NetworkException in case nodes cannot be added to the route, e.g. because they are not directly connected. This
  750.      *             can be the case when the links in the network have changed, but the graph has not been rebuilt.
  751.      */
  752.     public final Route getShortestRouteBetween(final GtuType gtuType, final Node nodeFrom, final Node nodeTo,
  753.             final List<Node> nodesVia) throws NetworkException
  754.     {
  755.         return getShortestRouteBetween(gtuType, nodeFrom, nodeTo, nodesVia, LinkWeight.LENGTH_NO_CONNECTORS);
  756.     }

  757.     /**
  758.      * Calculate the shortest route between two nodes in the network, via a list of intermediate nodes. If no path exists from
  759.      * the start node to the end node via the intermediate nodes in the network, null is returned. This method returns a
  760.      * CompleteRoute, which includes all nodes to get from start to end. This method recalculates the graph.
  761.      * @param gtuType the GtuType for which to calculate the shortest route
  762.      * @param nodeFrom the start node.
  763.      * @param nodeTo the end node.
  764.      * @param nodesVia a number of nodes that the GTU has to pass between nodeFrom and nodeTo in the given order.
  765.      * @param linkWeight link weight.
  766.      * @return the shortest route between two nodes in the network, via the intermediate nodes. If no path exists from the start
  767.      *         node to the end node via the intermediate nodes in the network, null is returned.
  768.      * @throws NetworkException in case nodes cannot be added to the route, e.g. because they are not directly connected. This
  769.      *             can be the case when the links in the network have changed, but the graph has not been rebuilt.
  770.      */
  771.     public final Route getShortestRouteBetween(final GtuType gtuType, final Node nodeFrom, final Node nodeTo,
  772.             final List<Node> nodesVia, final LinkWeight linkWeight) throws NetworkException
  773.     {
  774.         Route route = new Route("Route for " + gtuType + " from " + nodeFrom + "to " + nodeTo + " via " + nodesVia.toString(),
  775.                 gtuType);
  776.         SimpleDirectedWeightedGraph<Node, Link> graph = getGraph(gtuType, linkWeight);
  777.         List<Node> nodes = new ArrayList<>();
  778.         nodes.add(nodeFrom);
  779.         nodes.addAll(nodesVia);
  780.         nodes.add(nodeTo);
  781.         Node from = nodeFrom;
  782.         route.addNode(nodeFrom);
  783.         for (int i = 1; i < nodes.size(); i++)
  784.         {
  785.             Node to = nodes.get(i);
  786.             GraphPath<Node, Link> path =
  787.                     linkWeight.getAStarHeuristic() == null ? DijkstraShortestPath.findPathBetween(graph, from, to)
  788.                             : new AStarShortestPath<>(graph, linkWeight.getAStarHeuristic()).getPath(from, to);
  789.             if (path == null)
  790.             {
  791.                 CategoryLogger.always().debug("Cannot find a path from " + nodeFrom + " via " + nodesVia + " to " + nodeTo
  792.                         + " (failing between " + from + " and " + to + ")");
  793.                 return null;
  794.             }
  795.             for (Link link : path.getEdgeList())
  796.             {
  797.                 if (!link.getEndNode().equals(route.destinationNode())
  798.                         && route.destinationNode().isConnectedTo(gtuType, link.getEndNode()))
  799.                 {
  800.                     route.addNode(link.getEndNode());
  801.                 }
  802.                 else if (!link.getStartNode().equals(route.destinationNode())
  803.                         && route.destinationNode().isConnectedTo(gtuType, link.getStartNode()))
  804.                 {
  805.                     route.addNode(link.getStartNode());
  806.                 }
  807.                 else
  808.                 {
  809.                     throw new NetworkException(
  810.                             "Cannot connect two links when calculating shortest route with intermediate nodes");
  811.                 }
  812.             }
  813.             from = to;
  814.         }
  815.         return route;
  816.     }

  817.     /**
  818.      * Returns the graph, possibly a stored one.
  819.      * @param gtuType GTU type
  820.      * @param linkWeight link weight
  821.      * @return SimpleDirectedWeightedGraph
  822.      */
  823.     private SimpleDirectedWeightedGraph<Node, Link> getGraph(final GtuType gtuType, final LinkWeight linkWeight)
  824.     {
  825.         if (linkWeight.isStatic())
  826.         {
  827.             return this.linkGraphs.get(() -> buildGraph(gtuType, linkWeight), gtuType, linkWeight);
  828.         }
  829.         return buildGraph(gtuType, linkWeight);
  830.     }

  831.     /**
  832.      * @return a defensive copy of the routeMap.
  833.      */
  834.     public final ImmutableMap<GtuType, Map<String, Route>> getRouteMap()
  835.     {
  836.         return new ImmutableHashMap<>(this.routeMap, Immutable.WRAP);
  837.     }

  838.     /**
  839.      * @return only to be used in the 'network' package for cloning.
  840.      */
  841.     final Map<GtuType, Map<String, Route>> getRawRouteMap()
  842.     {
  843.         return this.routeMap;
  844.     }

  845.     /**
  846.      * @param newRouteMap the routeMap to set, only to be used in the 'network' package for cloning.
  847.      */
  848.     public final void setRawRouteMap(final Map<GtuType, Map<String, Route>> newRouteMap)
  849.     {
  850.         this.routeMap = newRouteMap;
  851.     }

  852.     /***************************************************************************************/
  853.     /**************************************** GTUs *****************************************/
  854.     /***************************************************************************************/

  855.     @Override
  856.     public final void addGTU(final Gtu gtu)
  857.     {
  858.         this.gtuMap.put(gtu.getId(), gtu);
  859.         // TODO verify that gtu.getSimulator() equals getSimulator() ?
  860.         fireTimedEvent(Network.GTU_ADD_EVENT, gtu.getId(), getSimulator().getSimulatorTime());
  861.     }

  862.     @Override
  863.     public final void removeGTU(final Gtu gtu)
  864.     {
  865.         fireTimedEvent(Network.GTU_REMOVE_EVENT, gtu.getId(), getSimulator().getSimulatorTime());
  866.         this.gtuMap.remove(gtu.getId());
  867.     }

  868.     @Override
  869.     public final boolean containsGTU(final Gtu gtu)
  870.     {
  871.         return this.gtuMap.containsValue(gtu);
  872.     }

  873.     @Override
  874.     public final Gtu getGTU(final String gtuId)
  875.     {
  876.         return this.gtuMap.get(gtuId);
  877.     }

  878.     @Override
  879.     public final Set<Gtu> getGTUs()
  880.     {
  881.         // defensive copy
  882.         return new LinkedHashSet<>(this.gtuMap.values());
  883.     }

  884.     @Override
  885.     public final boolean containsGtuId(final String gtuId)
  886.     {
  887.         return this.gtuMap.containsKey(gtuId);
  888.     }

  889.     /**
  890.      * @return gtuMap
  891.      */
  892.     final Map<String, Gtu> getRawGtuMap()
  893.     {
  894.         return this.gtuMap;
  895.     }

  896.     /***************************************************************************************/

  897.     /** Extra clearance around boundaries of network as fraction of width and height. */
  898.     public static final double EXTENT_MARGIN = 0.05;

  899.     /**
  900.      * Calculate the extent of the network based on the network objects' locations and return the dimensions.
  901.      * @return Rectangle2D.Double; the extent of the network
  902.      */
  903.     public Rectangle2D.Double getExtent()
  904.     {
  905.         double minX = Double.MAX_VALUE;
  906.         double minY = Double.MAX_VALUE;
  907.         double maxX = -Double.MAX_VALUE;
  908.         double maxY = -Double.MAX_VALUE;
  909.         boolean content = false;
  910.         for (Node node : this.nodeMap.values())
  911.         {
  912.             Bounds2d b = node.getBounds();
  913.             minX = Math.min(minX, node.getLocation().getX() + b.getMinX());
  914.             minY = Math.min(minY, node.getLocation().getY() + b.getMinY());
  915.             maxX = Math.max(maxX, node.getLocation().getX() + b.getMaxX());
  916.             maxY = Math.max(maxY, node.getLocation().getY() + b.getMaxY());
  917.             content = true;
  918.         }
  919.         for (Link link : this.linkMap.values())
  920.         {
  921.             Bounds2d b = link.getBounds();
  922.             minX = Math.min(minX, link.getLocation().getX() + b.getMinX());
  923.             minY = Math.min(minY, link.getLocation().getY() + b.getMinY());
  924.             maxX = Math.max(maxX, link.getLocation().getX() + b.getMaxX());
  925.             maxY = Math.max(maxY, link.getLocation().getY() + b.getMaxY());
  926.             content = true;
  927.         }
  928.         for (LocatedObject object : this.objectMap.values())
  929.         {
  930.             Bounds2d b = object.getBounds();
  931.             minX = Math.min(minX, object.getLocation().getX() + b.getMinX());
  932.             minY = Math.min(minY, object.getLocation().getY() + b.getMinY());
  933.             maxX = Math.max(maxX, object.getLocation().getX() + b.getMaxX());
  934.             maxY = Math.max(maxY, object.getLocation().getY() + b.getMaxY());
  935.             content = true;
  936.         }
  937.         if (content)
  938.         {
  939.             double relativeMargin = EXTENT_MARGIN;
  940.             double xMargin = relativeMargin * (maxX - minX);
  941.             double yMargin = relativeMargin * (maxY - minY);
  942.             return new Rectangle2D.Double(minX - xMargin / 2, minY - yMargin / 2, maxX - minX + xMargin, maxY - minY + yMargin);
  943.         }
  944.         else
  945.         {
  946.             return new Rectangle2D.Double(-500, -500, 1000, 1000);
  947.         }
  948.     }

  949.     @Override
  950.     public final String toString()
  951.     {
  952.         return "Network [id=" + this.id + ", nodeMapSize=" + this.nodeMap.size() + ", linkMapSize=" + this.linkMap.size()
  953.                 + ", objectMapSize=" + this.objectMap.size() + ", routeMapSize=" + this.routeMap.size() + ", gtuMapSize="
  954.                 + this.gtuMap.size() + "]";
  955.     }

  956.     /***************************************************************************************/
  957.     /*************************************** EVENTS ****************************************/
  958.     /***************************************************************************************/

  959.     /**
  960.      * The timed event type for pub/sub indicating the removal of a GTU from the network. <br>
  961.      * Payload: String gtuId (not an array, just a String)
  962.      */
  963.     public static final EventType GTU_REMOVE_EVENT = new EventType("Network.GTU.REMOVE",
  964.             new MetaData("GTU removed", "GTU removed", new ObjectDescriptor("GTU id", "GTU id", String.class)));

  965.     /**
  966.      * The timed event type for pub/sub indicating the addition of a GTU to the network. <br>
  967.      * Payload: String gtuId (not an array, just a String)
  968.      */
  969.     public static final EventType GTU_ADD_EVENT = new EventType("Network.GTU.ADD",
  970.             new MetaData("GTU added", "GTU added", new ObjectDescriptor("GTU id", "GTU id", String.class)));

  971.     /**
  972.      * The timed event type for pub/sub indicating the removal of a Route for a gtuType. <br>
  973.      * Payload: [String gtuTypeId, String routeId]
  974.      */
  975.     public static final EventType ROUTE_REMOVE_EVENT = new EventType("Network.ROUTE.REMOVE",
  976.             new MetaData("Route removed", "Route removed",
  977.                     new ObjectDescriptor[] {new ObjectDescriptor("GTU Type id", "GTU Type id", String.class),
  978.                             new ObjectDescriptor("Route id", "Route id", String.class)}));

  979.     /**
  980.      * The timed event type for pub/sub indicating the addition of a Route for a gtuType. <br>
  981.      * Payload: [String gtuTypeId, String routeId]
  982.      */
  983.     public static final EventType ROUTE_ADD_EVENT = new EventType("Network.ROUTE.ADD",
  984.             new MetaData("Route added", "Route added",
  985.                     new ObjectDescriptor[] {new ObjectDescriptor("GTU Type id", "GTU Type id", String.class),
  986.                             new ObjectDescriptor("Route id", "Route id", String.class)}));

  987.     /**
  988.      * The timed event type for pub/sub indicating the removal of a NonLocatedObject implementing object. <br>
  989.      * Payload: String objectId (not an array, just a String)
  990.      */
  991.     public static final EventType NONLOCATED_OBJECT_REMOVE_EVENT = new EventType("Network.NONLOCATED_OBJECT.REMOVE",
  992.             new MetaData("Non-located object removed", "Non-located, stationary object removed",
  993.                     new ObjectDescriptor("NonLocatedObject", "Id of non-located, stationary object", String.class)));

  994.     /**
  995.      * The timed event type for pub/sub indicating the addition of a NonLocatedObject implementing object. <br>
  996.      * Payload: String ObjectId (not an array, just a String)
  997.      */
  998.     public static final EventType NONLOCATED_OBJECT_ADD_EVENT = new EventType("Network.NONLOCATED_OBJECT.ADD",
  999.             new MetaData("Non-located object added", "Non-located, stationary object added",
  1000.                     new ObjectDescriptor("NonLocatedObject", "Id of non-located, stationary object", String.class)));

  1001.     /**
  1002.      * The timed event type for pub/sub indicating the removal of an ObjectInterface implementing object. <br>
  1003.      * Payload: String objectId (not an array, just a String)
  1004.      */
  1005.     public static final EventType OBJECT_REMOVE_EVENT =
  1006.             new EventType("Network.OBJECT.REMOVE", new MetaData("Object removed", "Visible, stationary object removed",
  1007.                     new ObjectDescriptor("id of Static object", "id of Visible, stationary object", String.class)));

  1008.     /**
  1009.      * The timed event type for pub/sub indicating the addition of an ObjectInterface implementing object. <br>
  1010.      * Payload: String ObjectId (not an array, just a String)
  1011.      */
  1012.     public static final EventType OBJECT_ADD_EVENT =
  1013.             new EventType("Network.OBJECT.ADD", new MetaData("Object added", "Visible, stationary object added",
  1014.                     new ObjectDescriptor("id of Static object", "id of Visible, stationary object", String.class)));

  1015.     /**
  1016.      * The timed event type for pub/sub indicating the removal of a Link. <br>
  1017.      * Payload: String linkId (not an array, just a String)
  1018.      */
  1019.     public static final EventType LINK_REMOVE_EVENT = new EventType("Network.LINK.REMOVE",
  1020.             new MetaData("Link removed", "Link removed", new ObjectDescriptor("Link", "Name of link", String.class)));

  1021.     /**
  1022.      * The timed event type for pub/sub indicating the addition of a Link. <br>
  1023.      * Payload: String linkId (not an array, just a String)
  1024.      */
  1025.     public static final EventType LINK_ADD_EVENT = new EventType("Network.LINK.ADD",
  1026.             new MetaData("Link added", "Link added", new ObjectDescriptor("Link", "Name of link", String.class)));

  1027.     /**
  1028.      * The timed event type for pub/sub indicating the removal of a Node. <br>
  1029.      * Payload: String nodeId (not an array, just a String)
  1030.      */
  1031.     public static final EventType NODE_REMOVE_EVENT = new EventType("Network.NODE.REMOVE",
  1032.             new MetaData("Node removed", "Node removed", new ObjectDescriptor("Node", "Name of node", String.class)));

  1033.     /**
  1034.      * The timed event type for pub/sub indicating the addition of a Node. <br>
  1035.      * Payload: String nodeId (not an array, just a String)
  1036.      */
  1037.     public static final EventType NODE_ADD_EVENT = new EventType("Network.NODE.ADD",
  1038.             new MetaData("Node added", "Node added", new ObjectDescriptor("Node", "Name of node", String.class)));

  1039. }