View Javadoc
1   package org.opentrafficsim.core.network;
2   
3   import java.io.Serializable;
4   import java.rmi.RemoteException;
5   import java.util.ArrayList;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import javax.naming.Binding;
13  import javax.naming.NamingEnumeration;
14  import javax.naming.NamingException;
15  import javax.naming.event.EventContext;
16  
17  import org.jgrapht.alg.DijkstraShortestPath;
18  import org.jgrapht.graph.SimpleDirectedWeightedGraph;
19  import org.opentrafficsim.core.animation.ClonableRenderable2DInterface;
20  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
21  import org.opentrafficsim.core.gtu.GTU;
22  import org.opentrafficsim.core.gtu.GTUType;
23  import org.opentrafficsim.core.network.route.CompleteRoute;
24  import org.opentrafficsim.core.network.route.Route;
25  import org.opentrafficsim.core.object.InvisibleObjectInterface;
26  import org.opentrafficsim.core.object.ObjectInterface;
27  import org.opentrafficsim.core.perception.PerceivableContext;
28  
29  import nl.tudelft.simulation.dsol.animation.Locatable;
30  import nl.tudelft.simulation.dsol.animation.D2.Renderable2DInterface;
31  import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
32  import nl.tudelft.simulation.event.EventProducer;
33  import nl.tudelft.simulation.immutablecollections.Immutable;
34  import nl.tudelft.simulation.immutablecollections.ImmutableHashMap;
35  import nl.tudelft.simulation.immutablecollections.ImmutableMap;
36  import nl.tudelft.simulation.naming.context.ContextUtil;
37  
38  /**
39   * A Network consists of a set of links. Each link has, in its turn, a start node and an end node.
40   * <p>
41   * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
42   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
43   * <p>
44   * $LastChangedDate: 2017-04-29 19:11:52 +0200 (Sat, 29 Apr 2017) $, @version $Revision: 3583 $, by $Author: averbraeck $,
45   * initial version Jul 22, 2015 <br>
46   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
47   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
48   * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
49   */
50  public class OTSNetwork extends EventProducer implements Network, PerceivableContext, Serializable
51  {
52      /** */
53      private static final long serialVersionUID = 20150722;
54  
55      /** Id of this network. */
56      private final String id;
57  
58      /** Map of Nodes. */
59      private Map<String, Node> nodeMap = new HashMap<>();
60  
61      /** Map of Links. */
62      private Map<String, Link> linkMap = new HashMap<>();
63  
64      /** Map of ObjectInterface. */
65      private Map<String, ObjectInterface> objectMap = new HashMap<>();
66  
67      /** Map of InvisibleObjects. */
68      private Map<String, InvisibleObjectInterface> invisibleObjectMap = new HashMap<>();
69  
70      /** Map of Routes. */
71      private Map<GTUType, Map<String, Route>> routeMap = new HashMap<>();
72  
73      /** Graphs to calculate shortest paths per GTUType. */
74      private Map<GTUType, SimpleDirectedWeightedGraph<Node, LinkEdge<Link>>> linkGraphs = new HashMap<>();
75  
76      /** GTUs registered in this network. */
77      private Map<String, GTU> gtuMap = new HashMap<>();
78  
79      /**
80       * Construction of an empty network.
81       * @param id the network id.
82       */
83      public OTSNetwork(final String id)
84      {
85          this.id = id;
86      }
87  
88      /** {@inheritDoc} */
89      @Override
90      public final String getId()
91      {
92          return this.id;
93      }
94  
95      /***************************************************************************************/
96      /**************************************** NODES ****************************************/
97      /***************************************************************************************/
98  
99      /** {@inheritDoc} */
100     @Override
101     public final ImmutableMap<String, Node> getNodeMap()
102     {
103         return new ImmutableHashMap<String, Node>(this.nodeMap, Immutable.WRAP);
104     }
105 
106     /** {@inheritDoc} */
107     @Override
108     public final void addNode(final Node node) throws NetworkException
109     {
110         if (containsNode(node))
111         {
112             throw new NetworkException("Node " + node + " already registered in network " + this.id);
113         }
114         if (this.nodeMap.keySet().contains(node.getId()))
115         {
116             throw new NetworkException("Node with name " + node.getId() + " already registered in network " + this.id);
117         }
118         this.nodeMap.put(node.getId(), node);
119         fireEvent(Network.NODE_ADD_EVENT, node.getId());
120     }
121 
122     /** {@inheritDoc} */
123     @Override
124     public final void removeNode(final Node node) throws NetworkException
125     {
126         if (!containsNode(node))
127         {
128             throw new NetworkException("Node " + node + " not registered in network " + this.id);
129         }
130         fireEvent(Network.NODE_REMOVE_EVENT, node.getId());
131         this.nodeMap.remove(node.getId());
132     }
133 
134     /** {@inheritDoc} */
135     @Override
136     public final boolean containsNode(final Node node)
137     {
138         // System.out.println(node);
139         return this.nodeMap.keySet().contains(node.getId());
140     }
141 
142     /** {@inheritDoc} */
143     @Override
144     public final boolean containsNode(final String nodeId)
145     {
146         return this.nodeMap.keySet().contains(nodeId);
147     }
148 
149     /** {@inheritDoc} */
150     @Override
151     public final Node getNode(final String nodeId)
152     {
153         return this.nodeMap.get(nodeId);
154     }
155 
156     /***************************************************************************************/
157     /**************************************** LINKS ****************************************/
158     /***************************************************************************************/
159 
160     /** {@inheritDoc} */
161     @Override
162     public final ImmutableMap<String, Link> getLinkMap()
163     {
164         return new ImmutableHashMap<String, Link>(this.linkMap, Immutable.WRAP);
165     }
166 
167     /** {@inheritDoc} */
168     @Override
169     public final void addLink(final Link link) throws NetworkException
170     {
171         if (containsLink(link))
172         {
173             throw new NetworkException("Link " + link + " already registered in network " + this.id);
174         }
175         if (this.linkMap.keySet().contains(link.getId()))
176         {
177             throw new NetworkException("Link with name " + link.getId() + " already registered in network " + this.id);
178         }
179         if (!containsNode(link.getStartNode()) || !containsNode(link.getEndNode()))
180         {
181             throw new NetworkException(
182                     "Start node or end node of Link " + link.getId() + " not registered in network " + this.id);
183         }
184         this.linkMap.put(link.getId(), link);
185         fireEvent(Network.LINK_ADD_EVENT, link.getId());
186     }
187 
188     /** {@inheritDoc} */
189     @Override
190     public final void removeLink(final Link link) throws NetworkException
191     {
192         if (!containsLink(link))
193         {
194             throw new NetworkException("Link " + link + " not registered in network " + this.id);
195         }
196         fireEvent(Network.LINK_REMOVE_EVENT, link.getId());
197         this.linkMap.remove(link.getId());
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public final Link getLink(final Node node1, final Node node2)
203     {
204         for (Link link : this.linkMap.values())
205         {
206             if (link.getStartNode().equals(node1) && link.getEndNode().equals(node2))
207             {
208                 return link;
209             }
210         }
211         return null;
212     }
213 
214     /** {@inheritDoc} */
215     @Override
216     public final Link getLink(final String nodeId1, final String nodeId2) throws NetworkException
217     {
218         if (!containsNode(nodeId1))
219         {
220             throw new NetworkException("Node " + nodeId1 + " not in network " + this.id);
221         }
222         if (!containsNode(nodeId2))
223         {
224             throw new NetworkException("Node " + nodeId2 + " not in network " + this.id);
225         }
226         return getLink(getNode(nodeId1), getNode(nodeId2));
227     }
228 
229     /** {@inheritDoc} */
230     @Override
231     public final boolean containsLink(final Link link)
232     {
233         return this.linkMap.keySet().contains(link.getId());
234     }
235 
236     /** {@inheritDoc} */
237     @Override
238     public final boolean containsLink(final String linkId)
239     {
240         return this.linkMap.keySet().contains(linkId);
241     }
242 
243     /** {@inheritDoc} */
244     @Override
245     public final Link getLink(final String linkId)
246     {
247         return this.linkMap.get(linkId);
248     }
249 
250     /***************************************************************************************/
251     /************************ OBJECT INTERFACE IMPLEMENTING OBJECTS ************************/
252     /***************************************************************************************/
253 
254     /** {@inheritDoc} */
255     @Override
256     public final ImmutableMap<String, ObjectInterface> getObjectMap()
257     {
258         return new ImmutableHashMap<String, ObjectInterface>(this.objectMap, Immutable.WRAP);
259     }
260 
261     /** {@inheritDoc} */
262     @Override
263     public final ImmutableMap<String, ObjectInterface> getObjectMap(final Class<ObjectInterface> objectType)
264     {
265         Map<String, ObjectInterface> result = new HashMap<>();
266         for (String key : this.objectMap.keySet())
267         {
268             ObjectInterface o = this.objectMap.get(key);
269             if (objectType.isInstance(o))
270             {
271                 result.put(key, o);
272             }
273         }
274         return new ImmutableHashMap<String, ObjectInterface>(result, Immutable.WRAP);
275     }
276 
277     /** {@inheritDoc} */
278     @Override
279     public final void addObject(final ObjectInterface object) throws NetworkException
280     {
281         if (containsObject(object))
282         {
283             throw new NetworkException("Object " + object + " already registered in network " + this.id);
284         }
285         if (containsObject(object.getFullId()))
286         {
287             throw new NetworkException("Object with name " + object.getFullId() + " already registered in network " + this.id);
288         }
289         this.objectMap.put(object.getFullId(), object);
290         fireEvent(Network.OBJECT_ADD_EVENT, object.getFullId());
291     }
292 
293     /** {@inheritDoc} */
294     @Override
295     public final void removeObject(final ObjectInterface object) throws NetworkException
296     {
297         if (!containsObject(object))
298         {
299             throw new NetworkException("Object " + object + " not registered in network " + this.id);
300         }
301         fireEvent(Network.OBJECT_REMOVE_EVENT, object.getFullId());
302         this.objectMap.remove(object.getFullId());
303     }
304 
305     /** {@inheritDoc} */
306     @Override
307     public final boolean containsObject(final ObjectInterface object)
308     {
309         return this.objectMap.containsKey(object.getFullId());
310     }
311 
312     /**
313      * {@inheritDoc}
314      * <p>
315      * Note that the objectId should be the <b>fullId</b> of the object, including any additions such as lane ids, link ids,
316      * etc.
317      */
318     @Override
319     public final boolean containsObject(final String objectId)
320     {
321         return this.objectMap.containsKey(objectId);
322     }
323 
324     /***************************************************************************************/
325     /********************************* INVISIBLE OBJECTS ***********************************/
326     /***************************************************************************************/
327 
328     /** {@inheritDoc} */
329     @Override
330     public final ImmutableMap<String, InvisibleObjectInterface> getInvisibleObjectMap()
331     {
332         return new ImmutableHashMap<String, InvisibleObjectInterface>(this.invisibleObjectMap, Immutable.WRAP);
333     }
334 
335     /** {@inheritDoc} */
336     @Override
337     public final ImmutableMap<String, InvisibleObjectInterface> getInvisibleObjectMap(
338             final Class<InvisibleObjectInterface> objectType)
339     {
340         Map<String, InvisibleObjectInterface> result = new HashMap<>();
341         for (String key : this.objectMap.keySet())
342         {
343             InvisibleObjectInterface o = this.invisibleObjectMap.get(key);
344             if (objectType.isInstance(o))
345             {
346                 result.put(key, o);
347             }
348         }
349         return new ImmutableHashMap<String, InvisibleObjectInterface>(result, Immutable.WRAP);
350     }
351 
352     /** {@inheritDoc} */
353     @Override
354     public final void addInvisibleObject(final InvisibleObjectInterface object) throws NetworkException
355     {
356         if (containsInvisibleObject(object))
357         {
358             throw new NetworkException("InvisibleObject " + object + " already registered in network " + this.id);
359         }
360         if (containsInvisibleObject(object.getFullId()))
361         {
362             throw new NetworkException(
363                     "InvisibleObject with name " + object.getFullId() + " already registered in network " + this.id);
364         }
365         this.invisibleObjectMap.put(object.getFullId(), object);
366         fireEvent(Network.INVISIBLE_OBJECT_ADD_EVENT, object.getFullId());
367     }
368 
369     /** {@inheritDoc} */
370     @Override
371     public final void removeInvisibleObject(final InvisibleObjectInterface object) throws NetworkException
372     {
373         if (!containsInvisibleObject(object))
374         {
375             throw new NetworkException("InvisibleObject " + object + " not registered in network " + this.id);
376         }
377         fireEvent(Network.INVISIBLE_OBJECT_REMOVE_EVENT, object.getFullId());
378         this.objectMap.remove(object.getFullId());
379     }
380 
381     /** {@inheritDoc} */
382     @Override
383     public final boolean containsInvisibleObject(final InvisibleObjectInterface object)
384     {
385         return this.invisibleObjectMap.containsKey(object.getFullId());
386     }
387 
388     /**
389      * {@inheritDoc}
390      * <p>
391      * Note that the objectId should be the <b>fullId</b> of the object, including any additions such as lane ids, link ids,
392      * etc.
393      */
394     @Override
395     public final boolean containsInvisibleObject(final String objectId)
396     {
397         return this.invisibleObjectMap.containsKey(objectId);
398     }
399 
400     /***************************************************************************************/
401     /*************************************** ROUTES ****************************************/
402     /***************************************************************************************/
403 
404     /** {@inheritDoc} */
405     @Override
406     public final ImmutableMap<String, Route> getDefinedRouteMap(final GTUType gtuType)
407     {
408         Map<String, Route> routes = new HashMap<>();
409         if (this.routeMap.containsKey(gtuType))
410         {
411             routes.putAll(this.routeMap.get(gtuType));
412         }
413         return new ImmutableHashMap<String, Route>(routes, Immutable.WRAP);
414     }
415 
416     /** {@inheritDoc} */
417     @Override
418     public final void addRoute(final GTUType gtuType, final Route route) throws NetworkException
419     {
420         if (containsRoute(gtuType, route))
421         {
422             throw new NetworkException(
423                     "Route " + route + " for GTUType " + gtuType + " already registered in network " + this.id);
424         }
425         if (this.routeMap.containsKey(gtuType) && this.routeMap.get(gtuType).keySet().contains(route.getId()))
426         {
427             throw new NetworkException("Route with name " + route.getId() + " for GTUType " + gtuType
428                     + " already registered in network " + this.id);
429         }
430         for (Node node : route.getNodes())
431         {
432             if (!containsNode(node))
433             {
434                 throw new NetworkException("Node " + node.getId() + " of route " + route.getId() + " for GTUType " + gtuType
435                         + " not registered in network " + this.id);
436             }
437         }
438         if (!this.routeMap.containsKey(gtuType))
439         {
440             this.routeMap.put(gtuType, new HashMap<String, Route>());
441         }
442         this.routeMap.get(gtuType).put(route.getId(), route);
443         fireEvent(Network.ROUTE_ADD_EVENT, new Object[] { gtuType.getId(), route.getId() });
444     }
445 
446     /** {@inheritDoc} */
447     @Override
448     public final void removeRoute(final GTUType gtuType, final Route route) throws NetworkException
449     {
450         if (!containsRoute(gtuType, route))
451         {
452             throw new NetworkException("Route " + route + " for GTUType " + gtuType + " not registered in network " + this.id);
453         }
454         fireEvent(Network.ROUTE_REMOVE_EVENT, new Object[] { gtuType.getId(), route.getId() });
455         this.routeMap.get(gtuType).remove(route.getId());
456     }
457 
458     /** {@inheritDoc} */
459     @Override
460     public final boolean containsRoute(final GTUType gtuType, final Route route)
461     {
462         if (this.routeMap.containsKey(gtuType))
463         {
464             return this.routeMap.get(gtuType).values().contains(route);
465         }
466         return false;
467     }
468 
469     /** {@inheritDoc} */
470     @Override
471     public final boolean containsRoute(final GTUType gtuType, final String routeId)
472     {
473         if (this.routeMap.containsKey(gtuType))
474         {
475             return this.routeMap.get(gtuType).keySet().contains(routeId);
476         }
477         return false;
478     }
479 
480     /** {@inheritDoc} */
481     @Override
482     public final Route getRoute(final GTUType gtuType, final String routeId)
483     {
484         if (this.routeMap.containsKey(gtuType))
485         {
486             return this.routeMap.get(gtuType).get(routeId);
487         }
488         return null;
489     }
490 
491     /** {@inheritDoc} */
492     @Override
493     public final Set<Route> getRoutesBetween(final GTUType gtuType, final Node nodeFrom, final Node nodeTo)
494     {
495         Set<Route> routes = new HashSet<>();
496         if (this.routeMap.containsKey(gtuType))
497         {
498             for (Route route : this.routeMap.get(gtuType).values())
499             {
500                 try
501                 {
502                     if (route.originNode().equals(nodeFrom) && route.destinationNode().equals(nodeTo))
503                     {
504                         routes.add(route);
505                     }
506                 }
507                 catch (NetworkException ne)
508                 {
509                     // thrown if no nodes exist in the route. Do not add the route in that case.
510                 }
511             }
512         }
513         return routes;
514     }
515 
516     /** {@inheritDoc} */
517     @Override
518     public final void buildGraph(final GTUType gtuType)
519     {
520         // TODO take connections into account, and possibly do node expansion to build the graph
521         @SuppressWarnings("rawtypes")
522         Class linkEdgeClass = LinkEdge.class;
523         @SuppressWarnings("unchecked")
524         SimpleDirectedWeightedGraph<Node, LinkEdge<Link>> graph =
525                 new SimpleDirectedWeightedGraph<Node, LinkEdge<Link>>(linkEdgeClass);
526         for (Node node : this.nodeMap.values())
527         {
528             graph.addVertex(node);
529         }
530         for (Link link : this.linkMap.values())
531         {
532             // determine if the link is accessible for the GTUType , and in which direction(s)
533             LongitudinalDirectionality directionality = link.getDirectionality(gtuType);
534             if (directionality.isForwardOrBoth())
535             {
536                 LinkEdge<Link> linkEdge = new LinkEdge<>(link);
537                 graph.addEdge(link.getStartNode(), link.getEndNode(), linkEdge);
538                 graph.setEdgeWeight(linkEdge, link.getLength().doubleValue());
539             }
540             if (directionality.isBackwardOrBoth())
541             {
542                 LinkEdge<Link> linkEdge = new LinkEdge<>(link);
543                 graph.addEdge(link.getEndNode(), link.getStartNode(), linkEdge);
544                 graph.setEdgeWeight(linkEdge, link.getLength().doubleValue());
545             }
546         }
547         this.linkGraphs.put(gtuType, graph);
548     }
549 
550     /** {@inheritDoc} */
551     @Override
552     public final CompleteRoute getShortestRouteBetween(final GTUType gtuType, final Node nodeFrom, final Node nodeTo)
553             throws NetworkException
554     {
555         CompleteRoute route = new CompleteRoute("Route for " + gtuType + " from " + nodeFrom + "to " + nodeTo, gtuType);
556         SimpleDirectedWeightedGraph<Node, LinkEdge<Link>> graph = this.linkGraphs.get(gtuType);
557         if (graph == null)
558         {
559             buildGraph(gtuType);
560             graph = this.linkGraphs.get(gtuType);
561         }
562         DijkstraShortestPath<Node, LinkEdge<Link>> path = new DijkstraShortestPath<>(graph, nodeFrom, nodeTo);
563         if (path.getPath() == null)
564         {
565             return null;
566         }
567         route.addNode(nodeFrom);
568         for (LinkEdge<Link> link : path.getPathEdgeList())
569         {
570             if (!link.getLink().getEndNode().equals(route.destinationNode())
571                     && route.destinationNode().isDirectionallyConnectedTo(gtuType, link.getLink().getEndNode()))
572             {
573                 route.addNode(link.getLink().getEndNode());
574             }
575             else if (!link.getLink().getStartNode().equals(route.destinationNode())
576                     && route.destinationNode().isDirectionallyConnectedTo(gtuType, link.getLink().getStartNode()))
577             {
578                 route.addNode(link.getLink().getStartNode());
579             }
580             else
581             {
582                 throw new NetworkException("Cannot connect two links when calculating shortest route");
583             }
584         }
585         return route;
586     }
587 
588     /** {@inheritDoc} */
589     @Override
590     public final CompleteRoute getShortestRouteBetween(final GTUType gtuType, final Node nodeFrom, final Node nodeTo,
591             final List<Node> nodesVia) throws NetworkException
592     {
593         CompleteRoute route = new CompleteRoute(
594                 "Route for " + gtuType + " from " + nodeFrom + "to " + nodeTo + " via " + nodesVia.toString(), gtuType);
595         SimpleDirectedWeightedGraph<Node, LinkEdge<Link>> graph = this.linkGraphs.get(gtuType);
596         if (graph == null)
597         {
598             buildGraph(gtuType);
599             graph = this.linkGraphs.get(gtuType);
600         }
601         List<Node> nodes = new ArrayList<>();
602         nodes.add(nodeFrom);
603         nodes.addAll(nodesVia);
604         nodes.add(nodeTo);
605         Node from = nodeFrom;
606         route.addNode(nodeFrom);
607         for (int i = 1; i < nodes.size(); i++)
608         {
609             Node to = nodes.get(i);
610             DijkstraShortestPath<Node, LinkEdge<Link>> path = new DijkstraShortestPath<>(graph, from, to);
611             if (path.getPath() == null)
612             {
613                 return null;
614             }
615             for (LinkEdge<Link> link : path.getPathEdgeList())
616             {
617                 if (!link.getLink().getEndNode().equals(route.destinationNode())
618                         && route.destinationNode().isDirectionallyConnectedTo(gtuType, link.getLink().getEndNode()))
619                 {
620                     route.addNode(link.getLink().getEndNode());
621                 }
622                 else if (!link.getLink().getStartNode().equals(route.destinationNode())
623                         && route.destinationNode().isDirectionallyConnectedTo(gtuType, link.getLink().getStartNode()))
624                 {
625                     route.addNode(link.getLink().getStartNode());
626                 }
627                 else
628                 {
629                     throw new NetworkException(
630                             "Cannot connect two links when calculating shortest route with intermediate nodes");
631                 }
632             }
633             from = to;
634         }
635         return route;
636     }
637 
638     /***************************************************************************************/
639     /**************************************** GTUs *****************************************/
640     /***************************************************************************************/
641 
642     /** {@inheritDoc} */
643     @Override
644     public final void addGTU(final GTU gtu)
645     {
646         this.gtuMap.put(gtu.getId(), gtu);
647         fireTimedEvent(Network.GTU_ADD_EVENT, gtu.getId(), gtu.getSimulator().getSimulatorTime());
648     }
649 
650     /** {@inheritDoc} */
651     @Override
652     public final void removeGTU(final GTU gtu)
653     {
654         fireTimedEvent(Network.GTU_REMOVE_EVENT, gtu.getId(), gtu.getSimulator().getSimulatorTime());
655         this.gtuMap.remove(gtu.getId());
656     }
657 
658     /** {@inheritDoc} */
659     @Override
660     public final boolean containsGTU(final GTU gtu)
661     {
662         return this.gtuMap.containsValue(gtu);
663     }
664 
665     /** {@inheritDoc} */
666     @Override
667     public final GTU getGTU(final String gtuId)
668     {
669         return this.gtuMap.get(gtuId);
670     }
671 
672     /** {@inheritDoc} */
673     @Override
674     public final Set<GTU> getGTUs()
675     {
676         // defensive copy
677         return new HashSet<GTU>(this.gtuMap.values());
678     }
679 
680     /** {@inheritDoc} */
681     @Override
682     public final boolean containsGtuId(final String gtuId)
683     {
684         return this.gtuMap.containsKey(gtuId);
685     }
686 
687     /** {@inheritDoc} */
688     @Override
689     public final String toString()
690     {
691         return "OTSNetwork [id=" + this.id + ", nodeMapSize=" + this.nodeMap.size() + ", linkMapSize=" + this.linkMap.size()
692                 + ", objectMapSize=" + this.objectMap.size() + ", routeMapSize=" + this.routeMap.size() + ", gtuMapSize="
693                 + this.gtuMap.size() + "]";
694     }
695 
696     /***************************************************************************************/
697     /*************************************** CLONE *****************************************/
698     /***************************************************************************************/
699 
700     /**
701      * Clone the OTSNetwork.
702      * @param newId the new id of the network
703      * @param oldSimulator the old simulator for this network
704      * @param newSimulator the new simulator for this network
705      * @param animation whether to (re)create animation or not
706      * @return a clone of this network
707      * @throws NetworkException in case the cloning fails
708      */
709     @SuppressWarnings("checkstyle:designforextension")
710     public OTSNetwork clone(final String newId, final OTSSimulatorInterface oldSimulator,
711             final OTSSimulatorInterface newSimulator, final boolean animation) throws NetworkException
712     {
713         OTSNetwork newNetwork = new OTSNetwork(newId);
714 
715         // clone the nodes
716         for (Node node : this.nodeMap.values())
717         {
718             ((OTSNode) node).clone1(newNetwork, newSimulator);
719         }
720 
721         // clone the links
722         for (Link oldLink : this.linkMap.values())
723         {
724             OTSLink newLink = ((OTSLink) oldLink).clone(newNetwork, newSimulator, animation);
725             if (animation)
726             {
727                 cloneAnimation(oldLink, newLink, oldSimulator, newSimulator);
728             }
729         }
730 
731         // make the link-connections for the cloned nodes
732         for (Node oldNode : this.nodeMap.values())
733         {
734             OTSNode newNode = ((OTSNode) oldNode).clone2(newNetwork, newSimulator, animation);
735             if (animation)
736             {
737                 cloneAnimation(oldNode, newNode, oldSimulator, newSimulator);
738             }
739         }
740 
741         // clone the graphs that had been created for the old network
742         for (GTUType gtuType : this.linkGraphs.keySet())
743         {
744             newNetwork.buildGraph(gtuType);
745         }
746 
747         // clone the routes
748         Map<GTUType, Map<String, Route>> newRouteMap = new HashMap<>();
749         for (GTUType gtuType : this.routeMap.keySet())
750         {
751             Map<String, Route> newRoutes = new HashMap<>();
752             for (Route route : this.routeMap.get(gtuType).values())
753             {
754                 newRoutes.put(route.getId(), route.clone(newNetwork, newSimulator, animation));
755             }
756             newRouteMap.put(gtuType, newRoutes);
757         }
758         newNetwork.routeMap = newRouteMap;
759         // clone the traffic lights
760         for (InvisibleObjectInterface io : getInvisibleObjectMap().values())
761         {
762             InvisibleObjectInterface clonedIO = io.clone(newSimulator, newNetwork);
763             newNetwork.addInvisibleObject(clonedIO);
764         }
765         return newNetwork;
766     }
767 
768     /**
769      * Clone all animation objects for the given class. The given class is the <b>source</b> of the animation objects, as it is
770      * not known on beforehand which objects need to be cloned. It is important for cloning that the animation objects implement
771      * the CloneableRenderable2DInterface, so they can be cloned with their properties. If not, they will not be taken into
772      * account for cloning by this method.
773      * @param oldSource the old source object that might have one or more animation objects attached to it
774      * @param newSource the new source object to attach the cloned animation objects to
775      * @param oldSimulator the old simulator when the old objects can be found
776      * @param newSimulator the new simulator where the new simulation objects need to be registered
777      */
778     @SuppressWarnings("checkstyle:designforextension")
779     public static void cloneAnimation(final Locatable oldSource, final Locatable newSource,
780             final OTSSimulatorInterface oldSimulator, final OTSSimulatorInterface newSimulator)
781     {
782         if (!(oldSimulator instanceof AnimatorInterface) || !(newSimulator instanceof AnimatorInterface))
783         {
784             return;
785         }
786 
787         try
788         {
789             EventContext context =
790                     (EventContext) ContextUtil.lookup(oldSimulator.getReplication().getContext(), "/animation/2D");
791             NamingEnumeration<Binding> list = context.listBindings("");
792             while (list.hasMore())
793             {
794                 Binding binding = list.next();
795                 Renderable2DInterface animationObject = (Renderable2DInterface) binding.getObject();
796                 Locatable locatable = animationObject.getSource();
797                 if (oldSource.equals(locatable) && animationObject instanceof ClonableRenderable2DInterface)
798                 {
799                     ((ClonableRenderable2DInterface) animationObject).clone(newSource, newSimulator);
800                 }
801             }
802         }
803         catch (NamingException | RemoteException exception)
804         {
805             System.err.println("Error when cloning animation objects for object " + oldSource);
806         }
807     }
808 
809     /**
810      * Remove all objects and animation in the network.
811      * @param simulator the simulator of the old network
812      */
813     @SuppressWarnings("checkstyle:designforextension")
814     public void destroy(final OTSSimulatorInterface simulator)
815     {
816         for (GTU gtu : this.getGTUs())
817         {
818             gtu.destroy();
819         }
820 
821         Set<Renderable2DInterface> animationObjects = new HashSet<>();
822         try
823         {
824             EventContext context = (EventContext) ContextUtil.lookup(simulator.getReplication().getContext(), "/animation/2D");
825             NamingEnumeration<Binding> list = context.listBindings("");
826             while (list.hasMore())
827             {
828                 Binding binding = list.next();
829                 Renderable2DInterface animationObject = (Renderable2DInterface) binding.getObject();
830                 animationObjects.add(animationObject);
831             }
832 
833             for (Renderable2DInterface ao : animationObjects)
834             {
835                 try
836                 {
837                     ao.destroy();
838                 }
839                 catch (Exception e)
840                 {
841                     //
842                 }
843             }
844         }
845         catch (NamingException | RemoteException exception)
846         {
847             System.err.println("Error when destroying animation objects");
848         }
849 
850         this.nodeMap.clear();
851         this.linkMap.clear();
852         this.linkGraphs.clear();
853         this.routeMap.clear();
854     }
855 
856     /**
857      * Remove all animation objects of the given class.
858      * @param clazz the class to remove the animation objects for
859      * @param oldSimulator the old simulator
860      */
861     @SuppressWarnings("checkstyle:designforextension")
862     public void removeAnimation(final Class<?> clazz, final OTSSimulatorInterface oldSimulator)
863     {
864         if (!(oldSimulator instanceof AnimatorInterface))
865         {
866             return;
867         }
868 
869         try
870         {
871             EventContext context =
872                     (EventContext) ContextUtil.lookup(oldSimulator.getReplication().getContext(), "/animation/2D");
873             NamingEnumeration<Binding> list = context.listBindings("");
874             while (list.hasMore())
875             {
876                 Binding binding = list.next();
877                 Renderable2DInterface animationObject = (Renderable2DInterface) binding.getObject();
878                 Locatable locatable = animationObject.getSource();
879                 if (clazz.isAssignableFrom(locatable.getClass()))
880                 {
881                     animationObject.destroy();
882                 }
883             }
884         }
885         catch (NamingException | RemoteException exception)
886         {
887             System.err.println("Error when destroying animation objects for class " + clazz.getSimpleName());
888         }
889     }
890 
891 }