View Javadoc
1   package org.opentrafficsim.demo.carFollowing;
2   
3   import java.awt.Color;
4   import java.awt.Container;
5   import java.awt.Frame;
6   import java.io.ByteArrayInputStream;
7   import java.io.IOException;
8   import java.rmi.RemoteException;
9   import java.util.ArrayList;
10  import java.util.List;
11  import java.util.Set;
12  
13  import javax.naming.NamingException;
14  import javax.xml.parsers.ParserConfigurationException;
15  
16  import org.djunits.unit.LengthUnit;
17  import org.djunits.unit.SpeedUnit;
18  import org.djunits.unit.TimeUnit;
19  import org.djunits.unit.UNITS;
20  import org.djunits.value.ValueException;
21  import org.djunits.value.vdouble.scalar.Acceleration;
22  import org.djunits.value.vdouble.scalar.DoubleScalar;
23  import org.djunits.value.vdouble.scalar.Duration;
24  import org.djunits.value.vdouble.scalar.Length;
25  import org.djunits.value.vdouble.scalar.Speed;
26  import org.djunits.value.vdouble.scalar.Time;
27  import org.opentrafficsim.base.modelproperties.CompoundProperty;
28  import org.opentrafficsim.base.modelproperties.ContinuousProperty;
29  import org.opentrafficsim.base.modelproperties.ProbabilityDistributionProperty;
30  import org.opentrafficsim.base.modelproperties.Property;
31  import org.opentrafficsim.base.modelproperties.PropertyException;
32  import org.opentrafficsim.base.modelproperties.SelectionProperty;
33  import org.opentrafficsim.base.parameters.ParameterException;
34  import org.opentrafficsim.core.distributions.Generator;
35  import org.opentrafficsim.core.dsol.OTSModelInterface;
36  import org.opentrafficsim.core.geometry.OTSGeometryException;
37  import org.opentrafficsim.core.gtu.GTU;
38  import org.opentrafficsim.core.gtu.GTUException;
39  import org.opentrafficsim.core.gtu.GTUType;
40  import org.opentrafficsim.core.gtu.animation.GTUColorer;
41  import org.opentrafficsim.core.network.NetworkException;
42  import org.opentrafficsim.core.network.OTSNetwork;
43  import org.opentrafficsim.core.network.route.Route;
44  import org.opentrafficsim.core.network.route.RouteGenerator;
45  import org.opentrafficsim.core.units.distributions.ContinuousDistDoubleScalar;
46  import org.opentrafficsim.graphs.LaneBasedGTUSampler;
47  import org.opentrafficsim.graphs.TrajectoryPlot;
48  import org.opentrafficsim.road.animation.AnimationToggles;
49  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedTemplateGTUType;
50  import org.opentrafficsim.road.gtu.lane.AbstractLaneBasedGTU;
51  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedCFLCTacticalPlannerFactory;
52  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedGTUFollowingDirectedChangeTacticalPlannerFactory;
53  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedGTUFollowingTacticalPlannerFactory;
54  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
55  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMOld;
56  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusFactory;
57  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusOld;
58  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.AbstractLaneChangeModel;
59  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Altruistic;
60  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Egoistic;
61  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLMRSPerceptionFactory;
62  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRSFactory;
63  import org.opentrafficsim.road.gtu.lane.tactical.toledo.ToledoFactory;
64  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
65  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
66  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlanner;
67  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlannerFactory;
68  import org.opentrafficsim.road.modelproperties.IDMPropertySet;
69  import org.opentrafficsim.road.network.factory.xml.XmlNetworkLaneParser;
70  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
71  import org.opentrafficsim.road.network.lane.Lane;
72  import org.opentrafficsim.road.network.lane.LaneType;
73  import org.opentrafficsim.simulationengine.AbstractWrappableAnimation;
74  import org.opentrafficsim.simulationengine.SimpleSimulatorInterface;
75  import org.xml.sax.SAXException;
76  
77  import nl.tudelft.simulation.dsol.SimRuntimeException;
78  import nl.tudelft.simulation.dsol.gui.swing.TablePanel;
79  import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;
80  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
81  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
82  import nl.tudelft.simulation.jstats.distributions.DistContinuous;
83  import nl.tudelft.simulation.jstats.distributions.DistErlang;
84  import nl.tudelft.simulation.jstats.streams.MersenneTwister;
85  import nl.tudelft.simulation.jstats.streams.StreamInterface;
86  
87  /**
88   * <p>
89   * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
90   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
91   * <p>
92   * $LastChangedDate: 2016-12-13 02:02:22 +0100 (Tue, 13 Dec 2016) $, @version $Revision: 2930 $, by $Author: wjschakel $,
93   * initial version 4 mrt. 2015 <br>
94   * @author <a href="http://Hansvanlint.weblog.tudelft.nl">Hans van Lint</a>
95   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
96   */
97  public class XMLNetworks2 extends AbstractWrappableAnimation implements UNITS
98  {
99      /** */
100     private static final long serialVersionUID = 20160422L;
101 
102     /** The model. */
103     private XMLNetwork2Model model;
104 
105     /**
106      * Define the XMLNetworks.
107      */
108     public XMLNetworks2()
109     {
110         this.properties.add(new SelectionProperty(
111                 "Network", "Network", "Network", new String[] { "Merge 1 plus 1 into 1", "Merge 2 plus 1 into 2",
112                         "Merge 2 plus 2 into 4", "Split 1 into 1 plus 1", "Split 2 into 1 plus 2", "Split 4 into 2 plus 2" },
113                 0, false, 0));
114         this.properties.add(new SelectionProperty("TacticalPlanner", "Tactical planner",
115                 "<html>The tactical planner determines if a lane change is desired and possible.</html>",
116                 new String[] { "MOBIL/IDM", "DIRECTED/IDM", "LMRS", "Toledo" }, 0, false, 600));
117         this.properties.add(new SelectionProperty("LaneChanging", "Lane changing",
118                 "<html>The lane change friendliness (if used -- eg just for MOBIL.</html>",
119                 new String[] { "Egoistic", "Altruistic" }, 0, false, 600));
120         this.properties.add(new ContinuousProperty("FlowPerInputLane", "Flow per input lane", "Traffic flow per input lane",
121                 500d, 0d, 3000d, "%.0f veh/h", false, 1));
122     }
123 
124     /** {@inheritDoc} */
125     @Override
126     public final void stopTimersThreads()
127     {
128         super.stopTimersThreads();
129         this.model = null;
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     protected final void addAnimationToggles()
135     {
136         AnimationToggles.setTextAnimationTogglesStandard(this);
137     }
138 
139     /** {@inheritDoc} */
140     @Override
141     protected final OTSModelInterface makeModel()
142     {
143         this.model = new XMLNetwork2Model(this.savedUserModifiedProperties);
144         return this.model;
145     }
146 
147     /** {@inheritDoc} */
148     @Override
149     protected final void addTabs(final SimpleSimulatorInterface simulator)
150     {
151         int graphCount = this.model.pathCount();
152         int columns = 1;
153         int rows = 0 == columns ? 0 : (int) Math.ceil(graphCount * 1.0 / columns);
154         TablePanel charts = new TablePanel(columns, rows);
155         for (int graphIndex = 0; graphIndex < graphCount; graphIndex++)
156         {
157             TrajectoryPlot tp = new TrajectoryPlot("Trajectories on lane " + (graphIndex + 1), new Duration(0.5, SECOND),
158                     this.model.getPath(graphIndex), simulator);
159             tp.setTitle("Trajectory Graph");
160             tp.setExtendedState(Frame.MAXIMIZED_BOTH);
161             LaneBasedGTUSampler graph = tp;
162             Container container = tp.getContentPane();
163             charts.setCell(container, graphIndex % columns, graphIndex / columns);
164             this.model.getPlots().add(graph);
165         }
166         addTab(getTabCount(), "statistics", charts);
167     }
168 
169     /** {@inheritDoc} */
170     @Override
171     public final String shortName()
172     {
173         return "Test networks - XML version";
174     }
175 
176     /** {@inheritDoc} */
177     @Override
178     public final String description()
179     {
180         return "<html><h1>Test Networks</h1>Prove that the test networks can be constructed and rendered on screen "
181                 + "and that a mix of cars and trucks can run on them.<br>On the statistics tab, a trajectory plot "
182                 + "is generated for each lane.</html>";
183     }
184 
185 }
186 
187 /**
188  * <p>
189  * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
190  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
191  * <p>
192  * $LastChangedDate: 2016-12-13 02:02:22 +0100 (Tue, 13 Dec 2016) $, @version $Revision: 2930 $, by $Author: wjschakel $,
193  * initial version mrt. 2015 <br>
194  * @author <a href="http://Hansvanlint.weblog.tudelft.nl">Hans van Lint</a>
195  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
196  */
197 class XMLNetwork2Model implements OTSModelInterface, UNITS
198 {
199     /** */
200     private static final long serialVersionUID = 20150304L;
201 
202     /** The simulator. */
203     private DEVSSimulatorInterface.TimeDoubleUnit simulator;
204 
205     /** The network. */
206     private final OTSNetwork network = new OTSNetwork("network");
207 
208     /** The plots. */
209     private List<LaneBasedGTUSampler> plots = new ArrayList<>();
210 
211     /** User settable properties. */
212     private List<Property<?>> properties = null;
213 
214     /** The sequence of Lanes that all vehicles will follow. */
215     private List<List<Lane>> paths = new ArrayList<>();
216 
217     /** The average headway (inter-vehicle time). */
218     private Duration averageHeadway;
219 
220     /** The minimum headway. */
221     private Duration minimumHeadway;
222 
223     /** The probability distribution for the variable part of the headway. */
224     DistContinuous headwayGenerator;
225 
226     /** The speed limit. */
227     private Speed speedLimit = new Speed(60, KM_PER_HOUR);
228 
229     /** Number of cars created. */
230     // private int carsCreated = 0;
231 
232     /** Type of all GTUs (required to permit lane changing). */
233     GTUType gtuType = GTUType.CAR;
234 
235     /** The car following model, e.g. IDM Plus for cars. */
236     private GTUFollowingModelOld carFollowingModelCars;
237 
238     /** The car following model, e.g. IDM Plus for trucks. */
239     private GTUFollowingModelOld carFollowingModelTrucks;
240 
241     /** The lane change model. */
242     AbstractLaneChangeModel laneChangeModel = new Egoistic();
243 
244     /** The probability that the next generated GTU is a passenger car. */
245     private double carProbability;
246 
247     /** Random stream. */
248     private StreamInterface stream = new MersenneTwister(12346);
249 
250     /** The random number generator used to decide what kind of GTU to generate. */
251     // private Random randomGenerator = new Random(12346);
252 
253     /** Probability distribution disttria(70,80,100). */
254     // private DistContinuous disttria = new DistTriangular(new MersenneTwister(), 70, 80, 100);
255 
256     /** The route generator. */
257     private RouteGenerator routeGenerator;
258 
259     /** Strategical planner generator for cars. */
260     private LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> strategicalPlannerGeneratorCars = null;
261 
262     /** Strategical planner generator for cars. */
263     private LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> strategicalPlannerGeneratorTrucks = null;
264 
265     /**
266      * @param userModifiedProperties ArrayList&lt;AbstractProperty&lt;?&gt;&gt;; the (possibly user modified) properties
267      */
268     XMLNetwork2Model(final List<Property<?>> userModifiedProperties)
269     {
270         this.properties = userModifiedProperties;
271     }
272 
273     /**
274      * @param index int; the rank number of the path
275      * @return List&lt;Lane&gt;; the set of lanes for the specified index
276      */
277     public final List<Lane> getPath(final int index)
278     {
279         return this.paths.get(index);
280     }
281 
282     /**
283      * Return the number of paths that can be used to show graphs.
284      * @return int; the number of paths that can be used to show graphs
285      */
286     public final int pathCount()
287     {
288         return this.paths.size();
289     }
290 
291     /**
292      * @return plots
293      */
294     public final List<LaneBasedGTUSampler> getPlots()
295     {
296         return this.plots;
297     }
298 
299     /** {@inheritDoc} */
300     @Override
301     public final void constructModel(final SimulatorInterface<Time, Duration, SimTimeDoubleUnit> theSimulator)
302             throws SimRuntimeException
303     {
304         this.simulator = (DEVSSimulatorInterface.TimeDoubleUnit) theSimulator;
305         try
306         {
307             CompoundProperty cp = new CompoundProperty("", "", "", this.properties, false, 0);
308             String networkType = (String) cp.findByKey("Network").getValue();
309             boolean merge = networkType.startsWith("M");
310             int lanesOnMain = Integer.parseInt(networkType.split(" ")[merge ? 1 : 5]);
311             int lanesOnBranch = Integer.parseInt(networkType.split(" ")[3]);
312             int lanesOnCommon = lanesOnMain + lanesOnBranch;
313             int lanesOnCommonCompressed = Integer.parseInt(networkType.split(" ")[merge ? 5 : 1]);
314 
315             LaneType laneType = LaneType.TWO_WAY_LANE;
316             // Get car-following model name
317             String carFollowingModelName = null;
318             CompoundProperty propertyContainer = new CompoundProperty("", "", "", this.properties, false, 0);
319             Property<?> cfmp = propertyContainer.findByKey("CarFollowingModel");
320             if (null == cfmp)
321             {
322                 throw new Error("Cannot find \"Car following model\" property");
323             }
324             if (cfmp instanceof SelectionProperty)
325             {
326                 carFollowingModelName = ((SelectionProperty) cfmp).getValue();
327             }
328             else
329             {
330                 throw new Error("\"Car following model\" property has wrong type");
331             }
332 
333             // Get car-following model parameter
334             for (Property<?> ap : new CompoundProperty("", "", "", this.properties, false, 0))
335             {
336                 if (ap instanceof CompoundProperty)
337                 {
338                     cp = (CompoundProperty) ap;
339                     if (ap.getKey().contains("IDM"))
340                     {
341                         // System.out.println("Car following model name appears to be " + ap.getKey());
342                         Acceleration a = IDMPropertySet.getA(cp);
343                         Acceleration b = IDMPropertySet.getB(cp);
344                         Length s0 = IDMPropertySet.getS0(cp);
345                         Duration tSafe = IDMPropertySet.getTSafe(cp);
346                         GTUFollowingModelOld gtuFollowingModel = null;
347                         if (carFollowingModelName.equals("IDM"))
348                         {
349                             gtuFollowingModel = new IDMOld(a, b, s0, tSafe, 1.0);
350                         }
351                         else if (carFollowingModelName.equals("IDM+"))
352                         {
353                             gtuFollowingModel = new IDMPlusOld(a, b, s0, tSafe, 1.0);
354                         }
355                         else
356                         {
357                             throw new Error("Unknown gtu following model: " + carFollowingModelName);
358                         }
359                         if (ap.getKey().contains("Car"))
360                         {
361                             this.carFollowingModelCars = gtuFollowingModel;
362                         }
363                         else if (ap.getKey().contains("Truck"))
364                         {
365                             this.carFollowingModelTrucks = gtuFollowingModel;
366                         }
367                         else
368                         {
369                             throw new Error("Cannot determine gtu type for " + ap.getKey());
370                         }
371                     }
372                 }
373             }
374 
375             // Get lane change model
376             cfmp = propertyContainer.findByKey("LaneChanging");
377             if (null == cfmp)
378             {
379                 throw new Error("Cannot find \"Lane changing\" property");
380             }
381             if (cfmp instanceof SelectionProperty)
382             {
383                 String laneChangeModelName = ((SelectionProperty) cfmp).getValue();
384                 if ("Egoistic".equals(laneChangeModelName))
385                 {
386                     this.laneChangeModel = new Egoistic();
387                 }
388                 else if ("Altruistic".equals(laneChangeModelName))
389                 {
390                     this.laneChangeModel = new Altruistic();
391                 }
392                 else
393                 {
394                     throw new Error("Lane changing " + laneChangeModelName + " not implemented");
395                 }
396             }
397             else
398             {
399                 throw new Error("\"Lane changing\" property has wrong type");
400             }
401 
402             StringBuilder xmlCode = new StringBuilder();
403             xmlCode.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE xml>\n");
404             xmlCode.append("<NETWORK xmlns=\"http://www.opentrafficsim.org/ots\" "
405                     + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
406             xmlCode.append("\txsi:schemaLocation=\"http://www.opentrafficsim.org/ots "
407                     + "http://www.opentrafficsim.org/docs/current/ots-network.xsd\">\n\n");
408             xmlCode.append("\t<DEFINITIONS>\n\n");
409             xmlCode.append("\t<GLOBAL />\n\n");
410             xmlCode.append("\t\t<GTUTYPE NAME=\"CAR\" />\n");
411             xmlCode.append("\t\t<GTUTYPE NAME=\"TRUCK\" />\n\n");
412             xmlCode.append("\t\t<GTU NAME=\"CAR\" GTUTYPE=\"CAR\" LENGTH=\"UNIF(4,7) m\" WIDTH=\"UNIF(1.7, 2) m\" "
413                     + "MAXSPEED=\"CONST(120) km/h\" />\n");
414             xmlCode.append("\t\t<GTU NAME=\"TRUCK\" GTUTYPE=\"TRUCK\" LENGTH=\"UNIF(16,24) m\" WIDTH=\"UNIF(2.2, 2.7) m\" "
415                     + "MAXSPEED=\"CONST(100) km/h\" />\n\n");
416             xmlCode.append("\t\t<GTUMIX NAME=\"gtumix\">\n");
417             xmlCode.append("\t\t\t<GTU NAME=\"CAR\" WEIGHT=\"" + (100 * this.carProbability) + "\"></GTU>\n");
418             xmlCode.append("\t\t\t<GTU NAME=\"TRUCK\" WEIGHT=\"" + (100 - 100 * this.carProbability) + "\"></GTU>\n");
419             xmlCode.append("\t\t</GTUMIX>\n\n");
420 
421             xmlCode.append("\t\t<ROADTYPE NAME=\"NORMALROAD\" DEFAULTLANEWIDTH=\"3.5m\" DEFAULTLANEKEEPING=\"KEEPRIGHT\"\n"
422                     + "\t\t\tDEFAULTOVERTAKING=\"LEFTSET([CAR, TRUCK] OVERTAKE [ALL]) RIGHTSPEED(40 km/h)\">\n");
423             xmlCode.append("\t\t\t<SPEEDLIMIT GTUTYPE=\"CAR\" LEGALSPEEDLIMIT=\"60km/h\" />\n");
424             xmlCode.append("\t\t</ROADTYPE>\n");
425 
426             xmlCode.append(makeRoadLayout("BRANCH", lanesOnBranch));
427             xmlCode.append(makeRoadLayout("MAIN", lanesOnMain));
428             xmlCode.append(makeRoadLayout("COMMON", lanesOnCommon));
429             xmlCode.append(makeRoadLayout("COMMONCOMPRESSED", lanesOnCommonCompressed));
430 
431             xmlCode.append("\t</DEFINITIONS>\n");
432             xmlCode.append("\t<NODE NAME=\"From\" COORDINATE=\"(0,0,0)\" ANGLE=\"0 deg\" />\n");
433             xmlCode.append("\t<NODE NAME=\"End\" COORDINATE=\"(2000,0,0)\" ANGLE=\"0 deg\" />\n");
434             xmlCode.append("\t<NODE NAME=\"From2\" COORDINATE=\"(0,-50,0)\" ANGLE=\"0 deg\" />\n");
435             xmlCode.append("\t<NODE NAME=\"FirstVia\" COORDINATE=\"(500,0,0)\" ANGLE=\"0 deg\" />\n");
436             xmlCode.append("\t<NODE NAME=\"SecondVia\" COORDINATE=\"(1000,0,0)\" ANGLE=\"0 deg\" />\n");
437             xmlCode.append("\t<NODE NAME=\"End2\" COORDINATE=\"(2000,-50,0)\" ANGLE=\"0 deg\" />\n");
438 
439             // OTSNode from = new OTSNode(this.network, "From", new OTSPoint3D(0, 0, 0));
440             // OTSNode end = new OTSNode(this.network, "End", new OTSPoint3D(2000, 0, 0));
441             // OTSNode from2a = new OTSNode(this.network, "From2a", new OTSPoint3D(0, -50, 0));
442             // OTSNode from2b = new OTSNode(this.network, "From2b", new OTSPoint3D(490, -2, 0));
443             // OTSNode firstVia = new OTSNode(this.network, "FirstVia", new OTSPoint3D(500, 0, 0));
444             // OTSNode end2a = new OTSNode(this.network, "End2a", new OTSPoint3D(1020, -2, 0));
445             // OTSNode end2b = new OTSNode(this.network, "End2b", new OTSPoint3D(2000, -50, 0));
446             // OTSNode secondVia = new OTSNode(this.network, "SecondVia", new OTSPoint3D(1000, 0, 0));
447             if (merge)
448             {
449                 // provide a route -- at the merge point, the GTU can otherwise decide to "go back"
450                 xmlCode.append("\t<ROUTE NAME=\"ALL\" NODELIST=\"FirstVia SecondVia End\" />");
451                 xmlCode.append("\t<ROUTEMIX NAME=\"routemix\" >\n");
452                 xmlCode.append("\t\t<ROUTE WEIGHT=\"1\" NAME=\"ALL\" />\n");
453                 xmlCode.append("\t</ROUTEMIX>\n");
454                 // ArrayList<Node> mainRouteNodes = new ArrayList<>();
455                 // mainRouteNodes.add(firstVia);
456                 // mainRouteNodes.add(secondVia);
457                 // mainRouteNodes.add(end);
458                 // Route mainRoute = new Route("main", mainRouteNodes);
459                 // this.routeGenerator = new FixedRouteGenerator(mainRoute);
460             }
461             else
462             {
463                 // determine the routes
464                 xmlCode.append("\t<ROUTE NAME=\"From_End\" NODELIST=\"FirstVia SecondVia End\" />\n");
465                 xmlCode.append("\t<ROUTE NAME=\"From_End2\" NODELIST=\"FirstVia SecondVia End2\" />\n");
466                 xmlCode.append("\t<ROUTEMIX NAME=\"routemix\" >\n");
467                 xmlCode.append("\t\t<ROUTE WEIGHT=\"" + lanesOnMain + "\" NAME=\"toEnd\" />\n");
468                 xmlCode.append("\t\t<ROUTE WEIGHT=\"" + lanesOnBranch + "\" NAME=\"toEnd2\" />\n");
469                 xmlCode.append("\t</ROUTEMIX>\n");
470 
471                 // List<FrequencyAndObject<Route>> routeProbabilities = new ArrayList<>();
472                 //
473                 // ArrayList<Node> mainRouteNodes = new ArrayList<>();
474                 // mainRouteNodes.add(firstVia);
475                 // mainRouteNodes.add(secondVia);
476                 // mainRouteNodes.add(end);
477                 // Route mainRoute = new Route("main", mainRouteNodes);
478                 // routeProbabilities.add(new FrequencyAndObject<>(lanesOnMain, mainRoute));
479                 //
480                 // ArrayList<Node> sideRouteNodes = new ArrayList<>();
481                 // sideRouteNodes.add(firstVia);
482                 // sideRouteNodes.add(secondVia);
483                 // sideRouteNodes.add(end2a);
484                 // sideRouteNodes.add(end2b);
485                 // Route sideRoute = new Route("side", sideRouteNodes);
486                 // routeProbabilities.add(new FrequencyAndObject<>(lanesOnBranch, sideRoute));
487                 // try
488                 // {
489                 // this.routeGenerator = new ProbabilisticRouteGenerator(routeProbabilities, new MersenneTwister(1234));
490                 // }
491                 // catch (ProbabilityException exception)
492                 // {
493                 // exception.printStackTrace();
494                 // }
495             }
496 
497             // Get remaining properties
498             for (Property<?> ap : new CompoundProperty("", "", "", this.properties, false, 0))
499             {
500                 if (ap instanceof SelectionProperty)
501                 {
502                     SelectionProperty sp = (SelectionProperty) ap;
503                     if ("TacticalPlanner".equals(sp.getKey()))
504                     {
505                         String tacticalPlannerName = sp.getValue();
506                         if ("IDM".equals(tacticalPlannerName))
507                         {
508                             new LaneBasedStrategicalRoutePlannerFactory(
509                                     new LaneBasedGTUFollowingTacticalPlannerFactory(this.carFollowingModelCars));
510                             new LaneBasedStrategicalRoutePlannerFactory(
511                                     new LaneBasedGTUFollowingTacticalPlannerFactory(this.carFollowingModelTrucks));
512                         }
513                         else if ("MOBIL/IDM".equals(tacticalPlannerName))
514                         {
515                             new LaneBasedStrategicalRoutePlannerFactory(
516                                     new LaneBasedCFLCTacticalPlannerFactory(this.carFollowingModelCars, this.laneChangeModel));
517                             new LaneBasedStrategicalRoutePlannerFactory(new LaneBasedCFLCTacticalPlannerFactory(
518                                     this.carFollowingModelTrucks, this.laneChangeModel));
519                         }
520                         else if ("DIRECTED/IDM".equals(tacticalPlannerName))
521                         {
522                             new LaneBasedStrategicalRoutePlannerFactory(
523                                     new LaneBasedGTUFollowingDirectedChangeTacticalPlannerFactory(this.carFollowingModelCars));
524                             new LaneBasedStrategicalRoutePlannerFactory(
525                                     new LaneBasedGTUFollowingDirectedChangeTacticalPlannerFactory(
526                                             this.carFollowingModelTrucks));
527                         }
528                         else if ("LMRS".equals(tacticalPlannerName))
529                         {
530                             // provide default parameters with the car-following model
531                             this.strategicalPlannerGeneratorCars = new LaneBasedStrategicalRoutePlannerFactory(
532                                     new LMRSFactory(new IDMPlusFactory(this.stream), new DefaultLMRSPerceptionFactory()));
533                             this.strategicalPlannerGeneratorTrucks = new LaneBasedStrategicalRoutePlannerFactory(
534                                     new LMRSFactory(new IDMPlusFactory(this.stream), new DefaultLMRSPerceptionFactory()));
535                         }
536                         else if ("Toledo".equals(tacticalPlannerName))
537                         {
538                             new LaneBasedStrategicalRoutePlannerFactory(new ToledoFactory());
539                             new LaneBasedStrategicalRoutePlannerFactory(new ToledoFactory());
540                         }
541                         else
542                         {
543                             throw new Error("Don't know how to create a " + tacticalPlannerName + " tactical planner");
544                         }
545                     }
546 
547                 }
548                 else if (ap instanceof ProbabilityDistributionProperty)
549                 {
550                     ProbabilityDistributionProperty pdp = (ProbabilityDistributionProperty) ap;
551                     String modelName = ap.getKey();
552                     if (modelName.equals("TrafficComposition"))
553                     {
554                         this.carProbability = pdp.getValue()[0];
555                     }
556                 }
557                 else if (ap instanceof ContinuousProperty)
558                 {
559                     ContinuousProperty contP = (ContinuousProperty) ap;
560                     if (contP.getKey().startsWith("Flow"))
561                     {
562                         this.averageHeadway = new Duration(3600.0 / contP.getValue(), SECOND);
563                         this.minimumHeadway = new Duration(3, SECOND);
564                         this.headwayGenerator = new DistErlang(new MersenneTwister(1234), 4,
565                                 DoubleScalar.minus(this.averageHeadway, this.minimumHeadway).getSI());
566                     }
567                 }
568                 else if (ap instanceof CompoundProperty)
569                 {
570                     CompoundProperty compoundProperty = (CompoundProperty) ap;
571                     if (ap.getKey().equals("Output"))
572                     {
573                         continue; // Output settings are handled elsewhere
574                     }
575                     if (ap.getKey().contains("IDM"))
576                     {
577                         Acceleration a = IDMPropertySet.getA(compoundProperty);
578                         Acceleration b = IDMPropertySet.getB(compoundProperty);
579                         Length s0 = IDMPropertySet.getS0(compoundProperty);
580                         Duration tSafe = IDMPropertySet.getTSafe(compoundProperty);
581                         GTUFollowingModelOld gtuFollowingModel = null;
582                         if (carFollowingModelName.equals("IDM"))
583                         {
584                             gtuFollowingModel = new IDMOld(a, b, s0, tSafe, 1.0);
585                         }
586                         else if (carFollowingModelName.equals("IDM+"))
587                         {
588                             gtuFollowingModel = new IDMPlusOld(a, b, s0, tSafe, 1.0);
589                         }
590                         else
591                         {
592                             throw new Error("Unknown gtu following model: " + carFollowingModelName);
593                         }
594                         if (ap.getKey().contains("Car"))
595                         {
596                             this.carFollowingModelCars = gtuFollowingModel;
597                         }
598                         else if (ap.getKey().contains("Truck"))
599                         {
600                             this.carFollowingModelTrucks = gtuFollowingModel;
601                         }
602                         else
603                         {
604                             throw new Error("Cannot determine gtu type for " + ap.getKey());
605                         }
606                     }
607                 }
608             }
609 
610             if (merge)
611             {
612                 xmlCode.append("\t<LINK NAME=\"From2 to FirstVia\" NODESTART=\"From2\" NODEEND=\"FirstVia\" "
613                         + "ROADLAYOUT=\"BRANCH\">\n");
614                 xmlCode.append("\t\t<STRAIGHT />\n");
615                 for (int lane = 1; lane <= lanesOnBranch; lane++)
616                 {
617                     xmlCode.append("\t\t<GENERATOR LANE=\"Lane" + lane + "\" POSITION=\"16m\" IAT=\"ERLANG(4,"
618                             + this.averageHeadway.minus(this.minimumHeadway).si + ") s\" "
619                             + "INITIALSPEED=\"UNIFORM(80,90) km/h\" GTUMIX=\"gtumix\" ROUTEMIX=\"routemix\"\n"
620                             + "\t\t\tGTUCOLORER=\"SWITCHABLE\" />\n");
621                     // Initial speed is not GTUType dependent.
622                 }
623                 xmlCode.append("\t</LINK>\n");
624             }
625             else
626             {
627                 xmlCode.append("\t<LINK NAME=\"SecondVia to End2\" NODESTART=\"SecondVia\" NODEEND=\"End2\" "
628                         + "ROADLAYOUT=\"BRANCH\">\n");
629                 xmlCode.append("\t\t<STRAIGHT />\n");
630                 // In the original simulation; the sinks were on a separate (invisible) lane
631                 for (int lane = 1; lane <= lanesOnBranch; lane++)
632                 {
633                     xmlCode.append("\t\t<SINK POSITION=\"999m\" LANE=\"Lane" + lane + "\" />\n");
634                 }
635                 xmlCode.append("\t</LINK>\n");
636                 // LaneFactory.makeMultiLaneBezier(this.network, "SecondVia to end2a", firstVia, secondVia, end2a, end2b,
637                 // lanesOnBranch, lanesOnCommon - lanesOnBranch, lanesOnCommon - lanesOnBranch, laneType, this.speedLimit,
638                 // this.simulator, LongitudinalDirectionality.DIR_PLUS);
639                 // setupSink(LaneFactory.makeMultiLane(this.network, "end2a to end2b", end2a, end2b, null, lanesOnBranch,
640                 // lanesOnCommon - lanesOnBranch, 0, laneType, this.speedLimit, this.simulator,
641                 // LongitudinalDirectionality.DIR_PLUS), laneType);
642             }
643             xmlCode.append(
644                     "\t<LINK NAME=\"From to FirstVia\" NODESTART=\"From2\" NODEEND=\"FirstVia\" " + "ROADLAYOUT=\"MAIN\">\n");
645             xmlCode.append("\t\t<STRAIGHT />\n");
646             for (int lane = 1; lane <= (merge ? lanesOnMain : lanesOnCommonCompressed); lane++)
647             {
648                 xmlCode.append("\t\t<GENERATOR LANE=\"Lane" + lane + "\" POSITION=\"16m\" IAT=\"ERLANG(4,"
649                         + this.averageHeadway.minus(this.minimumHeadway).si + ") s\" "
650                         + "INITIALSPEED=\"UNIFORM(80,90) km/h\" GTUMIX=\"gtumix\" ROUTEMIX=\"routemix\"\n"
651                         + "\t\t\tGTUCOLORER=\"SWITCHABLE\" />\n");
652                 // Initial speed is not GTUType dependent.
653             }
654             xmlCode.append("\t</LINK>\n");
655 
656             // Lane[] startLanes =
657             // LaneFactory.makeMultiLane(this.network, "From to FirstVia", from, firstVia, null, merge ? lanesOnMain
658             // : lanesOnCommonCompressed, laneType, this.speedLimit, this.simulator,
659             // LongitudinalDirectionality.DIR_PLUS);
660             // setupGenerator(startLanes);
661 
662             xmlCode.append("\t<LINK NAME=\"FirstVia to SecondVia\" NODESTART=\"FirstVia\" NODEEND=\"SecondVia\" "
663                     + "ROADLAYOUT=\"COMMON\">\n");
664             xmlCode.append("\t\t<STRAIGHT />\n");
665             for (int lane = lanesOnCommonCompressed + 1; lane <= lanesOnCommon; lane++)
666             {
667                 xmlCode.append("\t\t<BLOCK LANE=\"Lane" + lane + "\" POSITION=\"END-1mm\" />\n");
668             }
669             xmlCode.append("\t</LINK>\n");
670             // Lane[] common =
671             // LaneFactory.makeMultiLane(this.network, "FirstVia to SecondVia", firstVia, secondVia, null, lanesOnCommon,
672             // laneType, this.speedLimit, this.simulator, LongitudinalDirectionality.DIR_PLUS);
673             // if (merge)
674             // {
675             // for (int i = lanesOnCommonCompressed; i < lanesOnCommon; i++)
676             // {
677             // setupBlock(common[i]);
678             // }
679             xmlCode.append("\t<LINK NAME=\"SecondVia to End2\" NODESTART=\"SecondVia\" NODEEND=\"End2\" "
680                     + "ROADLAYOUT=\"BRANCH\">\n");
681             xmlCode.append("\t\t<STRAIGHT />\n");
682             // In the original simulation; the sinks were on a separate (invisible) lane
683             for (int lane = 1; lane <= lanesOnBranch; lane++)
684             {
685                 xmlCode.append("\t\t<SINK POSITION=\"999m\" LANE=\"Lane" + lane + "\" />\n");
686             }
687             xmlCode.append("\t</LINK>\n");
688 
689             // }
690             xmlCode.append(
691                     "\t<LINK NAME=\"SecondVia to End\" NODESTART=\"SecondVia\" NODEEND=\"End\" " + "ROADLAYOUT=\"BRANCH\">");
692             xmlCode.append("\t\t<STRAIGHT />\n");
693             // In the original simulation; the sinks were on a separate (invisible) lane
694             for (int lane = 1; lane <= lanesOnBranch; lane++)
695             {
696                 xmlCode.append("\t\t<SINK POSITION=\"999m\" LANE=\"Lane" + lane + "\" />\n");
697             }
698             xmlCode.append("\t</LINK>\n");
699             // setupSink(LaneFactory.makeMultiLane(this.network, "SecondVia to end", secondVia, end, null, merge
700             // ? lanesOnCommonCompressed : lanesOnMain, laneType, this.speedLimit, this.simulator,
701             // LongitudinalDirectionality.DIR_PLUS), laneType);
702 
703             // for (int index = 0; index < lanesOnCommon; index++)
704             // {
705             // this.paths.add(new ArrayList<Lane>());
706             // Lane lane = common[index];
707             // // Follow back
708             // while (lane.prevLanes(this.gtuType).size() > 0)
709             // {
710             // if (lane.prevLanes(this.gtuType).size() > 1)
711             // {
712             // throw new NetworkException("This network should not have lane merge points");
713             // }
714             // lane = lane.prevLanes(this.gtuType).keySet().iterator().next();
715             // }
716             // // Follow forward
717             // while (true)
718             // {
719             // this.paths.get(index).add(lane);
720             // int branching = lane.nextLanes(this.gtuType).size();
721             // if (branching == 0)
722             // {
723             // break;
724             // }
725             // if (branching > 1)
726             // {
727             // throw new NetworkException("This network should not have lane split points");
728             // }
729             // lane = lane.nextLanes(this.gtuType).keySet().iterator().next();
730             // }
731             // }
732             xmlCode.append("</NETWORK>\n");
733             XmlNetworkLaneParser nlp = new XmlNetworkLaneParser((DEVSSimulatorInterface.TimeDoubleUnit) theSimulator);
734 
735             System.out.println("Building network from XML description\n" + xmlCode.toString());
736             nlp.build(new ByteArrayInputStream(xmlCode.toString().getBytes()), this.network, true);
737             this.simulator.scheduleEventAbs(new Time(0.999, TimeUnit.BASE_SECOND), this, this, "drawGraphs", null);
738         }
739         catch (NamingException | NetworkException | GTUException | OTSGeometryException | PropertyException
740                 | ParserConfigurationException | SAXException | IOException | ValueException | ParameterException exception1)
741         {
742             exception1.printStackTrace();
743         }
744     }
745 
746     /** Width of a lane. */
747     static final double LANE_WIDTH = 3.5;
748 
749     /**
750      * @param name String; name of the road layout
751      * @param lanes int; number of lanes in the road layout
752      * @return String; XML code that represents the road layout
753      */
754     private String makeRoadLayout(final String name, final int lanes)
755     {
756         StringBuilder xmlCode = new StringBuilder();
757         xmlCode.append("\t\t<ROADLAYOUT NAME=\"" + name + "\" ROADTYPE=\"NORMALROAD\">\n");
758         xmlCode.append("\t\t\t<SHOULDER WIDTH=\"2m\" OFFSET=\"-1m\" COLOR=\"GREEN\" />\n");
759         double cumulativeOffset = 0;
760         for (int lane = 0; lane < lanes; lane++)
761         {
762             xmlCode.append("\t\t\t<STRIPE TYPE=\"" + (0 == lane ? "SOLID" : "DASHED") + "\" OFFSET=\"1m\" WIDTH=\"20cm\" />\n");
763             xmlCode.append("\t\t\t<LANE NAME=\"Lane" + (lane + 1) + "\" OFFSET=\"" + (cumulativeOffset + LANE_WIDTH / 2)
764                     + "m\" DIRECTION=\"FORWARD\" />\n");
765             cumulativeOffset += LANE_WIDTH;
766         }
767         xmlCode.append("\t\t\t<STRIPE TYPE=\"SOLID\" OFFSET=\"" + cumulativeOffset + "m\" WIDTH=\"20cm\" />\n");
768         xmlCode.append("\t\t\t<SHOULDER WIDTH=\"2m\" OFFSET=\"" + (cumulativeOffset + 1) + "m\" COLOR=\"GREEN\" />\n");
769         xmlCode.append("\t\t</ROADLAYOUT>\n");
770         return xmlCode.toString();
771     }
772 
773     /**
774      * @param stream the random stream to use
775      * @param lane reference lane to generate GTUs on
776      * @param lengthDistribution distribution of the GTU length
777      * @param widthDistribution distribution of the GTU width
778      * @param maximumSpeedDistribution distribution of the GTU's maximum speed
779      * @param initialPositions initial position(s) of the GTU on the Lane(s)
780      * @param strategicalPlannerFactory factory to generate the strategical planner for the GTU
781      * @return template for a GTU
782      * @throws GTUException when characteristics cannot be initialized
783      */
784     LaneBasedTemplateGTUType makeTemplate(final StreamInterface stream, final Lane lane,
785             final ContinuousDistDoubleScalar.Rel<Length, LengthUnit> lengthDistribution,
786             final ContinuousDistDoubleScalar.Rel<Length, LengthUnit> widthDistribution,
787             final ContinuousDistDoubleScalar.Rel<Speed, SpeedUnit> maximumSpeedDistribution,
788             final Set<DirectedLanePosition> initialPositions,
789             final LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> strategicalPlannerFactory) throws GTUException
790     {
791         return new LaneBasedTemplateGTUType(this.gtuType, new Generator<Length>()
792         {
793             @Override
794             public Length draw()
795             {
796                 return lengthDistribution.draw();
797             }
798         }, new Generator<Length>()
799         {
800             @Override
801             public Length draw()
802             {
803                 return widthDistribution.draw();
804             }
805         }, new Generator<Speed>()
806         {
807             @Override
808             public Speed draw()
809             {
810                 return maximumSpeedDistribution.draw();
811             }
812         },
813                 /*-new Generator<LaneBasedStrategicalPlanner>()
814                 {
815                     public LaneBasedStrategicalPlanner draw() throws ProbabilityException, ParameterException
816                     {
817                         BehavioralCharacteristics behavioralCharacteristics = DefaultsFactory.getDefaultBehavioralCharacteristics();
818                         behavioralCharacteristics.setParameter(ParameterTypes.LOOKAHEAD, new Length(450.0, LengthUnit.METER));
819                         try
820                         {
821                             return new LaneBasedStrategicalRoutePlanner(behavioralCharacteristics, tacticalPlanner,
822                                 XMLNetworkModel.this.routeGenerator.draw());
823                         }
824                         catch (GTUException exception)
825                         {
826                             throw new ParameterException(exception);
827                         }
828                     }
829                 }*/
830                 strategicalPlannerFactory, this.routeGenerator);
831 
832     }
833 
834     /**
835      * Notify the contour plots that the underlying data has changed.
836      */
837     protected final void drawGraphs()
838     {
839         for (LaneBasedGTUSampler plot : this.plots)
840         {
841             plot.reGraph();
842         }
843         // Re schedule this method
844         try
845         {
846             this.simulator.scheduleEventAbs(new Time(this.simulator.getSimulatorTime().getSI() + 1, TimeUnit.BASE_SECOND),
847                     this, this, "drawGraphs", null);
848         }
849         catch (SimRuntimeException exception)
850         {
851             exception.printStackTrace();
852         }
853 
854     }
855 
856     /**
857      * Generate cars at a fixed rate (implemented by re-scheduling this method).
858      * @param lane Lane; the lane on which the generated cars are placed
859      */
860     // protected final void generateCar(final Lane lane)
861     // {
862     // Length initialPosition = new Length(16, METER);
863     // Speed initialSpeed = new Speed(50, KM_PER_HOUR);
864     // boolean generate = true;
865     // // Check if there is sufficient room
866     // // Find the first vehicle on the lane
867     // LaneBasedGTU leader = null;
868     // Time when = new Time(this.simulator.getSimulatorTime().si, TimeUnit.SI);
869     // try
870     // {
871     // leader = lane.getGtuAhead(initialPosition, GTUDirectionality.DIR_PLUS, RelativePosition.REAR, when);
872     // if (null != leader)
873     // {
874     // double headway =
875     // leader.fractionalPosition(lane, leader.getRear()) * lane.getLength().si - initialPosition.si - 15.0 / 2;
876     // if (headway < 0.1)
877     // {
878     // System.out.println("Not generating GTU due to insufficient room");
879     // generate = false;
880     // }
881     // double leaderSpeed = leader.getSpeed().si;
882     // if (leaderSpeed < initialSpeed.si)
883     // {
884     // // What distance will it take to reduce speed to 0 with a decent deceleration?
885     // double decentDeceleration = 5; // [m/s/s]
886     // double deltaT = initialSpeed.si / decentDeceleration;
887     // double distance = 0.5 * decentDeceleration * deltaT * deltaT;
888     // if (distance > headway)
889     // {
890     // System.out.println("Not generating GTU due to slow driving GTU within emergency stop range");
891     // generate = false;
892     // }
893     // }
894     // }
895     // }
896     // catch (GTUException exception1)
897     // {
898     // exception1.printStackTrace();
899     // }
900     // try
901     // {
902     // if (generate)
903     // {
904     // boolean generateTruck = this.randomGenerator.nextDouble() > this.carProbability;
905     // Set<DirectedLanePosition> initialPositions = new LinkedHashSet<>(1);
906     // initialPositions.add(new DirectedLanePosition(lane, initialPosition, GTUDirectionality.DIR_PLUS));
907     // Length vehicleLength = new Length(generateTruck ? 15 : 4, METER);
908     // GTUFollowingModel gtuFollowingModel = generateTruck ? this.carFollowingModelTrucks : this.carFollowingModelCars;
909     // double speed = this.disttria.draw();
910     //
911     // LaneBasedDrivingCharacteristics drivingCharacteristics =
912     // new LaneBasedDrivingCharacteristics(gtuFollowingModel, this.laneChangeModel);
913     // drivingCharacteristics.setForwardHeadwayDistance(new Length(450.0, LengthUnit.METER));
914     // LaneBasedStrategicalPlanner strategicalPlanner =
915     // new LaneBasedStrategicalRoutePlanner(drivingCharacteristics, this.tacticalPlanner,
916     // this.routeGenerator.draw());
917     // new LaneBasedIndividualGTU("" + (++this.carsCreated), this.gtuType, initialPositions, initialSpeed,
918     // vehicleLength, new Length(1.8, METER), new Speed(speed, KM_PER_HOUR), this.simulator,
919     // strategicalPlanner, new LanePerceptionFull(), DefaultCarAnimation.class, this.gtuColorer, this.network);
920     // }
921     // Object[] arguments = new Object[1];
922     // arguments[0] = lane;
923     // this.simulator.scheduleEventRel(new Duration(this.headwayGenerator.draw(), SECOND), this, this, "generateCar",
924     // arguments);
925     // }
926     // catch (SimRuntimeException | NamingException | NetworkException | GTUException | OTSGeometryException
927     // | ProbabilityException exception)
928     // {
929     // exception.printStackTrace();
930     // }
931     // }
932 
933     /** {@inheritDoc} */
934     @Override
935     public SimulatorInterface<Time, Duration, SimTimeDoubleUnit> getSimulator()
936     {
937         return this.simulator;
938     }
939 
940     /** {@inheritDoc} */
941     @Override
942     public OTSNetwork getNetwork()
943     {
944         return this.network;
945     }
946 
947     /**
948      * The route colorer to show whether GTUs stay on the main route or go right at the split.
949      * <p>
950      * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. <br>
951      * All rights reserved. <br>
952      * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
953      * </p>
954      * $LastChangedDate: 2016-12-13 02:02:22 +0100 (Tue, 13 Dec 2016) $, @version $Revision: 2930 $, by $Author: wjschakel $,
955      * initial version Jan 3, 2016 <br>
956      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
957      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
958      */
959     private class DirectionGTUColorer implements GTUColorer
960     {
961         /** The legend. */
962         private List<LegendEntry> legend = new ArrayList<>();
963 
964         /** ... */
965         DirectionGTUColorer()
966         {
967             super();
968             this.legend.add(new LegendEntry(Color.RED, "Right", "Go right"));
969             this.legend.add(new LegendEntry(Color.BLUE, "Main", "Main route"));
970         }
971 
972         /** {@inheritDoc} */
973         @Override
974         public Color getColor(final GTU gtu)
975         {
976             AbstractLaneBasedGTU laneBasedGTU = (AbstractLaneBasedGTU) gtu;
977             Route route = ((LaneBasedStrategicalRoutePlanner) laneBasedGTU.getStrategicalPlanner()).getRoute();
978             if (route == null)
979             {
980                 return Color.black;
981             }
982             if (route.toString().toLowerCase().contains("end2"))
983             {
984                 return Color.red;
985             }
986             if (route.toString().toLowerCase().contains("end"))
987             {
988                 return Color.blue;
989             }
990             return Color.black;
991         }
992 
993         /** {@inheritDoc} */
994         @Override
995         public List<LegendEntry> getLegend()
996         {
997             return this.legend;
998         }
999     }
1000 
1001 }