View Javadoc
1   package org.opentrafficsim.demo.carFollowing;
2   
3   import static org.opentrafficsim.road.gtu.lane.RoadGTUTypes.CAR;
4   
5   import java.awt.Container;
6   import java.awt.Frame;
7   import java.rmi.RemoteException;
8   import java.util.ArrayList;
9   import java.util.HashSet;
10  import java.util.LinkedHashSet;
11  import java.util.List;
12  import java.util.Random;
13  import java.util.Set;
14  
15  import javax.naming.NamingException;
16  import javax.swing.SwingUtilities;
17  
18  import org.djunits.unit.TimeUnit;
19  import org.djunits.unit.UNITS;
20  import org.djunits.value.vdouble.scalar.Acceleration;
21  import org.djunits.value.vdouble.scalar.Duration;
22  import org.djunits.value.vdouble.scalar.Length;
23  import org.djunits.value.vdouble.scalar.Speed;
24  import org.djunits.value.vdouble.scalar.Time;
25  import org.opentrafficsim.base.modelproperties.BooleanProperty;
26  import org.opentrafficsim.base.modelproperties.CompoundProperty;
27  import org.opentrafficsim.base.modelproperties.ContinuousProperty;
28  import org.opentrafficsim.base.modelproperties.IntegerProperty;
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.core.dsol.OTSDEVSSimulatorInterface;
34  import org.opentrafficsim.core.dsol.OTSModelInterface;
35  import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
36  import org.opentrafficsim.core.geometry.OTSGeometryException;
37  import org.opentrafficsim.core.geometry.OTSPoint3D;
38  import org.opentrafficsim.core.gtu.GTUDirectionality;
39  import org.opentrafficsim.core.gtu.GTUException;
40  import org.opentrafficsim.core.gtu.GTUType;
41  import org.opentrafficsim.core.gtu.animation.GTUColorer;
42  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
43  import org.opentrafficsim.core.network.LongitudinalDirectionality;
44  import org.opentrafficsim.core.network.NetworkException;
45  import org.opentrafficsim.core.network.OTSNetwork;
46  import org.opentrafficsim.core.network.OTSNode;
47  import org.opentrafficsim.core.network.route.Route;
48  import org.opentrafficsim.graphs.AccelerationContourPlot;
49  import org.opentrafficsim.graphs.ContourPlot;
50  import org.opentrafficsim.graphs.DensityContourPlot;
51  import org.opentrafficsim.graphs.FlowContourPlot;
52  import org.opentrafficsim.graphs.LaneBasedGTUSampler;
53  import org.opentrafficsim.graphs.SpeedContourPlot;
54  import org.opentrafficsim.graphs.TrajectoryPlot;
55  import org.opentrafficsim.road.animation.AnimationToggles;
56  import org.opentrafficsim.road.gtu.animation.DefaultCarAnimation;
57  import org.opentrafficsim.road.gtu.lane.LaneBasedIndividualGTU;
58  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedCFLCTacticalPlannerFactory;
59  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedGTUFollowingDirectedChangeTacticalPlannerFactory;
60  import org.opentrafficsim.road.gtu.lane.tactical.following.AbstractIDM;
61  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
62  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMOld;
63  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusFactory;
64  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusOld;
65  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.AbstractLaneChangeModel;
66  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Altruistic;
67  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Egoistic;
68  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLMRSPerceptionFactory;
69  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRSFactory;
70  import org.opentrafficsim.road.gtu.lane.tactical.toledo.ToledoFactory;
71  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
72  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
73  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlannerFactory;
74  import org.opentrafficsim.road.modelproperties.IDMPropertySet;
75  import org.opentrafficsim.road.network.factory.LaneFactory;
76  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
77  import org.opentrafficsim.road.network.lane.Lane;
78  import org.opentrafficsim.road.network.lane.LaneType;
79  import org.opentrafficsim.simulationengine.AbstractWrappableAnimation;
80  import org.opentrafficsim.simulationengine.OTSSimulationException;
81  import org.opentrafficsim.simulationengine.SimpleSimulatorInterface;
82  
83  import nl.tudelft.simulation.dsol.SimRuntimeException;
84  import nl.tudelft.simulation.dsol.gui.swing.TablePanel;
85  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
86  
87  /**
88   * Circular road simulation demo.
89   * <p>
90   * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
91   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
92   * <p>
93   * $LastChangedDate: 2017-04-29 15:37:05 +0200 (Sat, 29 Apr 2017) $, @version $Revision: 3579 $, by $Author: averbraeck $,
94   * initial version 21 nov. 2014 <br>
95   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
96   */
97  public class CircularRoad extends AbstractWrappableAnimation implements UNITS
98  {
99      /** */
100     private static final long serialVersionUID = 1L;
101 
102     /** The model. */
103     private RoadSimulationModel model;
104 
105     /**
106      * Create a CircularRoad simulation.
107      * @throws PropertyException when a property is not handled
108      */
109     public CircularRoad() throws PropertyException
110     {
111         this.properties.add(new SelectionProperty("LaneChanging", "Lane changing",
112                 "<html>The lane change strategies vary in politeness.<br>"
113                         + "Two types are implemented:<ul><li>Egoistic (looks only at personal gain).</li>"
114                         + "<li>Altruistic (assigns effect on new and current follower the same weight as "
115                         + "the personal gain).</html>",
116                 new String[] { "Egoistic", "Altruistic" }, 0, false, 500));
117         this.properties.add(new SelectionProperty("TacticalPlanner", "Tactical planner",
118                 "<html>The tactical planner determines if a lane change is desired and possible.</html>",
119                 new String[] { "IDM", "MOBIL/IDM", "DIRECTED/IDM", "LMRS", "Toledo" }, 1, false, 600));
120         this.properties.add(new IntegerProperty("TrackLength", "Track length", "Circumference of the track", 2000, 500, 6000,
121                 "Track length %dm", false, 10));
122         this.properties.add(new ContinuousProperty("MeanDensity", "Mean density", "Number of vehicles per km", 40.0, 5.0, 45.0,
123                 "Density %.1f veh/km", false, 11));
124         this.properties.add(new ContinuousProperty("DensityVariability", "Density variability",
125                 "Variability of the number of vehicles per km", 0.0, 0.0, 1.0, "%.1f", false, 12));
126         List<Property<?>> outputProperties = new ArrayList<Property<?>>();
127         for (int lane = 1; lane <= 2; lane++)
128         {
129             String laneId = String.format("Lane %d ", lane);
130             outputProperties.add(new BooleanProperty(laneId + "Density", laneId + " Density", laneId + "Density contour plot",
131                     true, false, 0));
132             outputProperties
133                     .add(new BooleanProperty(laneId + "Flow", laneId + " Flow", laneId + "Flow contour plot", true, false, 1));
134             outputProperties.add(
135                     new BooleanProperty(laneId + "Speed", laneId + " Speed", laneId + "Speed contour plot", true, false, 2));
136             outputProperties.add(new BooleanProperty(laneId + "Acceleration", laneId + " Acceleration",
137                     laneId + "Acceleration contour plot", true, false, 3));
138             outputProperties.add(new BooleanProperty(laneId + "Fixed Sample Rate Trajectories", laneId + " FSR Trajectories",
139                     laneId + "Trajectory (time/distance) diagram", true, false, 4));
140             outputProperties.add(new BooleanProperty(laneId + "Variable Sample Rate Trajectories", laneId + " VSR Trajectories",
141                     laneId + "Trajectory (time/distance) diagram", true, false, 5));
142         }
143         this.properties.add(new CompoundProperty("OutputGraphs", "Output graphs", "Select the graphical output",
144                 outputProperties, true, 1000));
145     }
146 
147     /** {@inheritDoc} */
148     @Override
149     public final void stopTimersThreads()
150     {
151         super.stopTimersThreads();
152         this.model = null;
153     }
154 
155     /**
156      * Main program.
157      * @param args String[]; the command line arguments (not used)
158      * @throws SimRuntimeException should never happen
159      */
160     public static void main(final String[] args) throws SimRuntimeException
161     {
162         SwingUtilities.invokeLater(new Runnable()
163         {
164             @Override
165             public void run()
166             {
167                 try
168                 {
169                     CircularRoad circularRoad = new CircularRoad();
170                     List<Property<?>> propertyList = circularRoad.getProperties();
171                     try
172                     {
173                         propertyList.add(new ProbabilityDistributionProperty("TrafficComposition", "Traffic composition",
174                                 "<html>Mix of passenger cars and trucks</html>", new String[] { "passenger car", "truck" },
175                                 new Double[] { 0.8, 0.2 }, false, 10));
176                     }
177                     catch (PropertyException exception)
178                     {
179                         exception.printStackTrace();
180                     }
181                     propertyList.add(new SelectionProperty("CarFollowingModel", "Car following model",
182                             "<html>The car following model determines "
183                                     + "the acceleration that a vehicle will make taking into account "
184                                     + "nearby vehicles, infrastructural restrictions (e.g. speed limit, "
185                                     + "curvature of the road) capabilities of the vehicle and personality "
186                                     + "of the driver.</html>",
187                             new String[] { "IDM", "IDM+" }, 1, false, 1));
188                     propertyList.add(IDMPropertySet.makeIDMPropertySet("IDMCar", "Car",
189                             new Acceleration(1.0, METER_PER_SECOND_2), new Acceleration(1.5, METER_PER_SECOND_2),
190                             new Length(2.0, METER), new Duration(1.0, SECOND), 2));
191                     propertyList.add(IDMPropertySet.makeIDMPropertySet("IDMTruck", "Truck",
192                             new Acceleration(0.5, METER_PER_SECOND_2), new Acceleration(1.25, METER_PER_SECOND_2),
193                             new Length(2.0, METER), new Duration(1.0, SECOND), 3));
194 
195                     circularRoad.buildAnimator(Time.ZERO, Duration.ZERO, new Duration(3600.0, SECOND), propertyList, null,
196                             true);
197                 }
198                 catch (SimRuntimeException | NamingException | OTSSimulationException | PropertyException exception)
199                 {
200                     exception.printStackTrace();
201                 }
202             }
203         });
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     protected final OTSModelInterface makeModel(final GTUColorer colorer)
209     {
210         this.model = new RoadSimulationModel(getSavedUserModifiedProperties(), colorer);
211         return this.model;
212     }
213 
214     /**
215      * @return the saved user properties for a next run
216      */
217     private List<Property<?>> getSavedUserModifiedProperties()
218     {
219         return this.savedUserModifiedProperties;
220     }
221 
222     /** {@inheritDoc} */
223     @Override
224     protected final void addAnimationToggles()
225     {
226         AnimationToggles.setTextAnimationTogglesStandard(this);
227     }
228 
229     /** {@inheritDoc} */
230     @Override
231     protected final void addTabs(final SimpleSimulatorInterface simulator) throws OTSSimulationException, PropertyException
232     {
233         // Make the tab with the plots
234         Property<?> output = new CompoundProperty("", "", "", this.properties, false, 0).findByKey("OutputGraphs");
235         if (null == output)
236         {
237             throw new Error("Cannot find output properties");
238         }
239         ArrayList<BooleanProperty> graphs = new ArrayList<BooleanProperty>();
240         if (output instanceof CompoundProperty)
241         {
242             CompoundProperty outputProperties = (CompoundProperty) output;
243             for (Property<?> ap : outputProperties.getValue())
244             {
245                 if (ap instanceof BooleanProperty)
246                 {
247                     BooleanProperty bp = (BooleanProperty) ap;
248                     if (bp.getValue())
249                     {
250                         graphs.add(bp);
251                     }
252                 }
253             }
254         }
255         else
256         {
257             throw new Error("output properties should be compound");
258         }
259 
260         int graphCount = graphs.size();
261         int columns = (int) Math.ceil(Math.sqrt(graphCount));
262         int rows = 0 == columns ? 0 : (int) Math.ceil(graphCount * 1.0 / columns);
263         TablePanel charts = new TablePanel(columns, rows);
264 
265         for (int i = 0; i < graphCount; i++)
266         {
267             String graphName = graphs.get(i).getKey();
268             Container container = null;
269             LaneBasedGTUSampler graph;
270             int pos = graphName.indexOf(' ') + 1;
271             String laneNumberText = graphName.substring(pos, pos + 1);
272             int lane = Integer.parseInt(laneNumberText) - 1;
273 
274             if (graphName.contains("Trajectories"))
275             {
276                 Duration sampleInterval = graphName.contains("Variable Sample Rate") ? null : new Duration(0.2, SECOND);
277                 TrajectoryPlot tp = new TrajectoryPlot(graphName, sampleInterval, this.model.getPath(lane), simulator);
278                 tp.setTitle("Trajectory Graph");
279                 tp.setExtendedState(Frame.MAXIMIZED_BOTH);
280                 graph = tp;
281                 container = tp.getContentPane();
282             }
283             else
284             {
285                 ContourPlot cp;
286                 if (graphName.contains("Density"))
287                 {
288                     cp = new DensityContourPlot(graphName, this.model.getPath(lane));
289                     cp.setTitle("Density Contour Graph");
290                 }
291                 else if (graphName.contains("Speed"))
292                 {
293                     cp = new SpeedContourPlot(graphName, this.model.getPath(lane));
294                     cp.setTitle("Speed Contour Graph");
295                 }
296                 else if (graphName.contains("Flow"))
297                 {
298                     cp = new FlowContourPlot(graphName, this.model.getPath(lane));
299                     cp.setTitle("Flow Contour Graph");
300                 }
301                 else if (graphName.contains("Acceleration"))
302                 {
303                     cp = new AccelerationContourPlot(graphName, this.model.getPath(lane));
304                     cp.setTitle("Acceleration Contour Graph");
305                 }
306                 else
307                 {
308                     throw new Error("Unhandled type of contourplot: " + graphName);
309                 }
310                 graph = cp;
311                 container = cp.getContentPane();
312             }
313             // Add the container to the matrix
314             charts.setCell(container, i % columns, i / columns);
315             this.model.getPlots().add(graph);
316         }
317         addTab(getTabCount(), "statistics", charts);
318     }
319 
320     /** {@inheritDoc} */
321     @Override
322     public final String shortName()
323     {
324         return "Circular Road simulation";
325     }
326 
327     /** {@inheritDoc} */
328     @Override
329     public final String description()
330     {
331         return "<html><h1>Circular Road simulation</h1>" + "Vehicles are unequally distributed over a two lane ring road.<br>"
332                 + "When simulation starts, all vehicles begin driving, some lane changes will occurr and some "
333                 + "shockwaves should develop.<br>"
334                 + "Trajectories and contourplots are generated during the simulation for both lanes.</html>";
335     }
336 
337 }
338 
339 /**
340  * Simulate traffic on a circular, two-lane road.
341  * <p>
342  * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
343  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
344  * <p>
345  * $LastChangedDate: 2017-04-29 15:37:05 +0200 (Sat, 29 Apr 2017) $, @version $Revision: 3579 $, by $Author: averbraeck $,
346  * initial version 1 nov. 2014 <br>
347  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
348  */
349 class RoadSimulationModel implements OTSModelInterface, UNITS
350 {
351     /** */
352     private static final long serialVersionUID = 20141121L;
353 
354     /** The simulator. */
355     private OTSDEVSSimulatorInterface simulator;
356 
357     /** Number of cars created. */
358     private int carsCreated = 0;
359 
360     /** The car following model, e.g. IDM Plus for cars. */
361     private GTUFollowingModelOld carFollowingModelCars;
362 
363     /** The car following model, e.g. IDM Plus for trucks. */
364     private GTUFollowingModelOld carFollowingModelTrucks;
365 
366     /** The probability that the next generated GTU is a passenger car. */
367     private double carProbability;
368 
369     /** The lane change model. */
370     private AbstractLaneChangeModel laneChangeModel;
371 
372     /** Minimum distance. */
373     private Length minimumDistance = new Length(0, METER);
374 
375     /** The speed limit. */
376     private Speed speedLimit = new Speed(100, KM_PER_HOUR);
377 
378     /** The plots. */
379     private List<LaneBasedGTUSampler> plots = new ArrayList<>();
380 
381     /** User settable properties. */
382     private List<Property<?>> properties = null;
383 
384     /** The sequence of Lanes that all vehicles will follow. */
385     private List<List<Lane>> paths = new ArrayList<>();
386 
387     /** The random number generator used to decide what kind of GTU to generate. */
388     private Random randomGenerator = new Random(12345);
389 
390     /** The GTUColorer for the generated vehicles. */
391     private final GTUColorer gtuColorer;
392 
393     /** Strategical planner generator for cars. */
394     private LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> strategicalPlannerGeneratorCars = null;
395 
396     /** Strategical planner generator for cars. */
397     private LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> strategicalPlannerGeneratorTrucks = null;
398 
399     /** The OTSNetwork. */
400     private final OTSNetwork network = new OTSNetwork("network");
401 
402     /**
403      * @param properties ArrayList&lt;AbstractProperty&lt;?&gt;&gt;; the properties
404      * @param gtuColorer the default and initial GTUColorer, e.g. a DefaultSwitchableTUColorer
405      */
406     RoadSimulationModel(final List<Property<?>> properties, final GTUColorer gtuColorer)
407     {
408         this.properties = properties;
409         this.gtuColorer = gtuColorer;
410     }
411 
412     /**
413      * @param index int; the rank number of the path
414      * @return List&lt;Lane&gt;; the set of lanes for the specified index
415      */
416     public List<Lane> getPath(final int index)
417     {
418         return this.paths.get(index);
419     }
420 
421     /** {@inheritDoc} */
422     @Override
423     public void constructModel(final SimulatorInterface<Time, Duration, OTSSimTimeDouble> theSimulator)
424             throws SimRuntimeException, RemoteException
425     {
426         final int laneCount = 2;
427         for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
428         {
429             this.paths.add(new ArrayList<Lane>());
430         }
431         this.simulator = (OTSDEVSSimulatorInterface) theSimulator;
432         double radius = 6000 / 2 / Math.PI;
433         double headway = 40;
434         double headwayVariability = 0;
435         try
436         {
437 
438             // Get car-following model name
439             String carFollowingModelName = null;
440             CompoundProperty propertyContainer = new CompoundProperty("", "", "", this.properties, false, 0);
441             Property<?> cfmp = propertyContainer.findByKey("CarFollowingModel");
442             if (null == cfmp)
443             {
444                 throw new Error("Cannot find \"Car following model\" property");
445             }
446             if (cfmp instanceof SelectionProperty)
447             {
448                 carFollowingModelName = ((SelectionProperty) cfmp).getValue();
449             }
450             else
451             {
452                 throw new Error("\"Car following model\" property has wrong type");
453             }
454 
455             // Get car-following model parameter
456             for (Property<?> ap : new CompoundProperty("", "", "", this.properties, false, 0))
457             {
458                 if (ap instanceof CompoundProperty)
459                 {
460                     CompoundProperty cp = (CompoundProperty) ap;
461                     System.out.println("Checking compound property " + cp);
462                     if (ap.getKey().contains("IDM"))
463                     {
464                         System.out.println("Car following model name appears to be " + ap.getKey());
465                         Acceleration a = IDMPropertySet.getA(cp);
466                         Acceleration b = IDMPropertySet.getB(cp);
467                         Length s0 = IDMPropertySet.getS0(cp);
468                         Duration tSafe = IDMPropertySet.getTSafe(cp);
469                         GTUFollowingModelOld gtuFollowingModel = null;
470                         if (carFollowingModelName.equals("IDM"))
471                         {
472                             gtuFollowingModel = new IDMOld(a, b, s0, tSafe, 1.0);
473                         }
474                         else if (carFollowingModelName.equals("IDM+"))
475                         {
476                             gtuFollowingModel = new IDMPlusOld(a, b, s0, tSafe, 1.0);
477                         }
478                         else
479                         {
480                             throw new Error("Unknown gtu following model: " + carFollowingModelName);
481                         }
482                         if (ap.getKey().contains("Car"))
483                         {
484                             this.carFollowingModelCars = gtuFollowingModel;
485                         }
486                         else if (ap.getKey().contains("Truck"))
487                         {
488                             this.carFollowingModelTrucks = gtuFollowingModel;
489                         }
490                         else
491                         {
492                             throw new Error("Cannot determine gtu type for " + ap.getKey());
493                         }
494                     }
495                 }
496             }
497 
498             // Get lane change model
499             cfmp = propertyContainer.findByKey("LaneChanging");
500             if (null == cfmp)
501             {
502                 throw new Error("Cannot find \"Lane changing\" property");
503             }
504             if (cfmp instanceof SelectionProperty)
505             {
506                 String laneChangeModelName = ((SelectionProperty) cfmp).getValue();
507                 if ("Egoistic".equals(laneChangeModelName))
508                 {
509                     this.laneChangeModel = new Egoistic();
510                 }
511                 else if ("Altruistic".equals(laneChangeModelName))
512                 {
513                     this.laneChangeModel = new Altruistic();
514                 }
515                 else
516                 {
517                     throw new Error("Lane changing " + laneChangeModelName + " not implemented");
518                 }
519             }
520             else
521             {
522                 throw new Error("\"Lane changing\" property has wrong type");
523             }
524 
525             // Get remaining properties
526             for (Property<?> ap : new CompoundProperty("", "", "", this.properties, false, 0))
527             {
528                 if (ap instanceof SelectionProperty)
529                 {
530                     SelectionProperty sp = (SelectionProperty) ap;
531                     if ("TacticalPlanner".equals(sp.getKey()))
532                     {
533                         String tacticalPlannerName = sp.getValue();
534                         if ("MOBIL/IDM".equals(tacticalPlannerName))
535                         {
536                             this.strategicalPlannerGeneratorCars = new LaneBasedStrategicalRoutePlannerFactory(
537                                     new LaneBasedCFLCTacticalPlannerFactory(this.carFollowingModelCars, this.laneChangeModel));
538                             this.strategicalPlannerGeneratorTrucks =
539                                     new LaneBasedStrategicalRoutePlannerFactory(new LaneBasedCFLCTacticalPlannerFactory(
540                                             this.carFollowingModelTrucks, this.laneChangeModel));
541                         }
542                         else if ("DIRECTED/IDM".equals(tacticalPlannerName))
543                         {
544                             this.strategicalPlannerGeneratorCars = new LaneBasedStrategicalRoutePlannerFactory(
545                                     new LaneBasedGTUFollowingDirectedChangeTacticalPlannerFactory(this.carFollowingModelCars));
546                             this.strategicalPlannerGeneratorTrucks = new LaneBasedStrategicalRoutePlannerFactory(
547                                     new LaneBasedGTUFollowingDirectedChangeTacticalPlannerFactory(
548                                             this.carFollowingModelTrucks));
549                         }
550                         else if ("LMRS".equals(tacticalPlannerName))
551                         {
552                             // provide default parameters with the car-following model
553                             BehavioralCharacteristics defaultBehavioralCFCharacteristics = new BehavioralCharacteristics();
554                             defaultBehavioralCFCharacteristics.setDefaultParameters(AbstractIDM.class);
555                             this.strategicalPlannerGeneratorCars =
556                                     new LaneBasedStrategicalRoutePlannerFactory(new LMRSFactory(new IDMPlusFactory(),
557                                             defaultBehavioralCFCharacteristics, new DefaultLMRSPerceptionFactory()));
558                             this.strategicalPlannerGeneratorTrucks =
559                                     new LaneBasedStrategicalRoutePlannerFactory(new LMRSFactory(new IDMPlusFactory(),
560                                             defaultBehavioralCFCharacteristics, new DefaultLMRSPerceptionFactory()));
561                         }
562                         else if ("Toledo".equals(tacticalPlannerName))
563                         {
564                             this.strategicalPlannerGeneratorCars =
565                                     new LaneBasedStrategicalRoutePlannerFactory(new ToledoFactory());
566                             this.strategicalPlannerGeneratorTrucks =
567                                     new LaneBasedStrategicalRoutePlannerFactory(new ToledoFactory());
568                         }
569                         else
570                         {
571                             throw new Error("Don't know how to create a " + tacticalPlannerName + " tactical planner");
572                         }
573                     }
574                 }
575                 else if (ap instanceof ProbabilityDistributionProperty)
576                 {
577                     ProbabilityDistributionProperty pdp = (ProbabilityDistributionProperty) ap;
578                     if (ap.getKey().equals("TrafficComposition"))
579                     {
580                         this.carProbability = pdp.getValue()[0];
581                     }
582                 }
583                 else if (ap instanceof IntegerProperty)
584                 {
585                     IntegerProperty ip = (IntegerProperty) ap;
586                     if ("TrackLength".equals(ip.getKey()))
587                     {
588                         radius = ip.getValue() / 2 / Math.PI;
589                     }
590                 }
591                 else if (ap instanceof ContinuousProperty)
592                 {
593                     ContinuousProperty cp = (ContinuousProperty) ap;
594                     if (cp.getKey().equals("MeanDensity"))
595                     {
596                         headway = 1000 / cp.getValue();
597                     }
598                     if (cp.getKey().equals("DensityVariability"))
599                     {
600                         headwayVariability = cp.getValue();
601                     }
602                 }
603                 else if (ap instanceof CompoundProperty)
604                 {
605                     if (ap.getKey().equals("OutputGraphs"))
606                     {
607                         continue; // Output settings are handled elsewhere
608                     }
609                 }
610             }
611             GTUType gtuType = CAR;
612             Set<GTUType> compatibility = new HashSet<GTUType>();
613             compatibility.add(gtuType);
614             LaneType laneType = new LaneType("CarLane", compatibility);
615             OTSNode start = new OTSNode(this.network, "Start", new OTSPoint3D(radius, 0, 0));
616             OTSNode halfway = new OTSNode(this.network, "Halfway", new OTSPoint3D(-radius, 0, 0));
617 
618             OTSPoint3D[] coordsHalf1 = new OTSPoint3D[127];
619             for (int i = 0; i < coordsHalf1.length; i++)
620             {
621                 double angle = Math.PI * (1 + i) / (1 + coordsHalf1.length);
622                 coordsHalf1[i] = new OTSPoint3D(radius * Math.cos(angle), radius * Math.sin(angle), 0);
623             }
624             Lane[] lanes1 = LaneFactory.makeMultiLane(this.network, "FirstHalf", start, halfway, coordsHalf1, laneCount,
625                     laneType, this.speedLimit, this.simulator, LongitudinalDirectionality.DIR_PLUS);
626             OTSPoint3D[] coordsHalf2 = new OTSPoint3D[127];
627             for (int i = 0; i < coordsHalf2.length; i++)
628             {
629                 double angle = Math.PI + Math.PI * (1 + i) / (1 + coordsHalf2.length);
630                 coordsHalf2[i] = new OTSPoint3D(radius * Math.cos(angle), radius * Math.sin(angle), 0);
631             }
632             Lane[] lanes2 = LaneFactory.makeMultiLane(this.network, "SecondHalf", halfway, start, coordsHalf2, laneCount,
633                     laneType, this.speedLimit, this.simulator, LongitudinalDirectionality.DIR_PLUS);
634             for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
635             {
636                 this.paths.get(laneIndex).add(lanes1[laneIndex]);
637                 this.paths.get(laneIndex).add(lanes2[laneIndex]);
638             }
639             // Put the (not very evenly spaced) cars on the track
640             double variability = (headway - 20) * headwayVariability;
641             System.out.println("headway is " + headway + " variability limit is " + variability);
642             Random random = new Random(12345);
643             for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
644             {
645                 double lane1Length = lanes1[laneIndex].getLength().getSI();
646                 double trackLength = lane1Length + lanes2[laneIndex].getLength().getSI();
647                 for (double pos = 0; pos <= trackLength - headway - variability;)
648                 {
649                     Lane lane = pos >= lane1Length ? lanes2[laneIndex] : lanes1[laneIndex];
650                     // Actual headway is uniformly distributed around headway
651                     double laneRelativePos = pos > lane1Length ? pos - lane1Length : pos;
652                     double actualHeadway = headway + (random.nextDouble() * 2 - 1) * variability;
653                     // System.out.println(lane + ", len=" + lane.getLength() + ", pos=" + laneRelativePos);
654                     generateCar(new Length(laneRelativePos, METER), lane, gtuType);
655                     pos += actualHeadway;
656                 }
657             }
658             // Schedule regular updates of the graph
659             this.simulator.scheduleEventAbs(new Time(9.999, TimeUnit.BASE_SECOND), this, this, "drawGraphs", null);
660         }
661         catch (SimRuntimeException | NamingException | NetworkException | GTUException | OTSGeometryException
662                 | PropertyException exception)
663         {
664             exception.printStackTrace();
665         }
666     }
667 
668     /**
669      * Notify the contour plots that the underlying data has changed.
670      */
671     protected final void drawGraphs()
672     {
673         for (LaneBasedGTUSampler plot : this.plots)
674         {
675             plot.reGraph();
676         }
677         // Re schedule this method
678         try
679         {
680             this.simulator.scheduleEventAbs(
681                     new Time(this.simulator.getSimulatorTime().get().getSI() + 10, TimeUnit.BASE_SECOND), this, this,
682                     "drawGraphs", null);
683         }
684         catch (SimRuntimeException exception)
685         {
686             exception.printStackTrace();
687         }
688 
689     }
690 
691     /**
692      * Generate one car.
693      * @param initialPosition Length; the initial position of the new cars
694      * @param lane Lane; the lane on which the new cars are placed
695      * @param gtuType GTUType&lt;String&gt;; the type of the new cars
696      * @throws NamingException on ???
697      * @throws SimRuntimeException cannot happen
698      * @throws NetworkException on network inconsistency
699      * @throws GTUException when something goes wrong during construction of the car
700      * @throws OTSGeometryException when the initial position is outside the center line of the lane
701      */
702     protected final void generateCar(final Length initialPosition, final Lane lane, final GTUType gtuType)
703             throws NamingException, NetworkException, SimRuntimeException, GTUException, OTSGeometryException
704     {
705 
706         // GTU itself
707         boolean generateTruck = this.randomGenerator.nextDouble() > this.carProbability;
708         Length vehicleLength = new Length(generateTruck ? 15 : 4, METER);
709         LaneBasedIndividualGTU gtu = new LaneBasedIndividualGTU("" + (++this.carsCreated), gtuType, vehicleLength,
710                 new Length(1.8, METER), new Speed(200, KM_PER_HOUR), this.simulator, this.network);
711         gtu.setNoLaneChangeDistance(Length.ZERO);
712 
713         // strategical planner
714         LaneBasedStrategicalPlanner strategicalPlanner;
715         Route route = null;
716         if (!generateTruck)
717         {
718             strategicalPlanner = this.strategicalPlannerGeneratorCars.create(gtu, route);
719         }
720         else
721         {
722             strategicalPlanner = this.strategicalPlannerGeneratorTrucks.create(gtu, route);
723         }
724 
725         // init
726         Set<DirectedLanePosition> initialPositions = new LinkedHashSet<>(1);
727         initialPositions.add(new DirectedLanePosition(lane, initialPosition, GTUDirectionality.DIR_PLUS));
728         Speed initialSpeed = new Speed(0, KM_PER_HOUR);
729         gtu.initWithAnimation(strategicalPlanner, initialPositions, initialSpeed, DefaultCarAnimation.class, this.gtuColorer);
730         // Check that the gtu is indeed at the expected position
731         /*-
732         Lane actualLane = gtu.getLanes().keySet().iterator().next();
733         if (actualLane != lane)
734         {
735             System.out.println("Newly created GTU already switched from lane" + lane + " to " + actualLane);
736         }
737         else
738         {
739             Length actualPosition = gtu.position(lane, gtu.getReference());
740             if (Math.abs(actualPosition.si - initialPosition.si) > 0.5)
741             {
742                 System.out.println("Newly created GTU jumped from " + initialPosition + " to " + actualPosition);
743             }
744             DirectedPoint actualLocation = gtu.getLocation();
745             DirectedPoint expectedLocation =
746                     lane.getCenterLine().extract(initialPosition, initialPosition.plus(new Length(0.0001, LengthUnit.METER)))
747                             .getLocation();
748             double distance = actualLocation.distance(expectedLocation);
749             if (distance > 0.5)
750             {
751                 System.out.println("Actual location of new GTU is " + actualLocation + ", expectedLocation is "
752                         + expectedLocation + " difference is " + distance + "m");
753             }
754         }
755          */
756     }
757 
758     /** {@inheritDoc} */
759     @Override
760     public SimulatorInterface<Time, Duration, OTSSimTimeDouble> getSimulator() throws RemoteException
761     {
762         return this.simulator;
763     }
764 
765     /** {@inheritDoc} */
766     @Override
767     public OTSNetwork getNetwork()
768     {
769         return this.network;
770     }
771 
772     /**
773      * @return plots
774      */
775     public final List<LaneBasedGTUSampler> getPlots()
776     {
777         return this.plots;
778     }
779 
780     /**
781      * @return minimumDistance
782      */
783     public final Length getMinimumDistance()
784     {
785         return this.minimumDistance;
786     }
787 
788     /**
789      * Stop simulation and throw an Error.
790      * @param theSimulator OTSDEVSSimulatorInterface; the simulator
791      * @param errorMessage String; the error message
792      */
793     public void stopSimulator(final OTSDEVSSimulatorInterface theSimulator, final String errorMessage)
794     {
795         System.out.println("Error: " + errorMessage);
796         try
797         {
798             if (theSimulator.isRunning())
799             {
800                 theSimulator.stop();
801             }
802         }
803         catch (SimRuntimeException exception)
804         {
805             exception.printStackTrace();
806         }
807         throw new Error(errorMessage);
808     }
809 
810 }