View Javadoc
1   package org.opentrafficsim.demo.carFollowing;
2   
3   import java.awt.Container;
4   import java.awt.Frame;
5   import java.awt.geom.Rectangle2D;
6   import java.rmi.RemoteException;
7   import java.util.ArrayList;
8   import java.util.Collection;
9   import java.util.HashMap;
10  import java.util.Iterator;
11  import java.util.Map;
12  import java.util.Random;
13  
14  import javax.naming.NamingException;
15  import javax.swing.SwingUtilities;
16  
17  import nl.tudelft.simulation.dsol.SimRuntimeException;
18  import nl.tudelft.simulation.dsol.gui.swing.TablePanel;
19  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
20  
21  import org.opentrafficsim.car.Car;
22  import org.opentrafficsim.car.lanechanging.AbstractLaneChangeModel;
23  import org.opentrafficsim.car.lanechanging.Altruistic;
24  import org.opentrafficsim.car.lanechanging.Egoistic;
25  import org.opentrafficsim.car.lanechanging.LaneChangeModel;
26  import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
27  import org.opentrafficsim.core.dsol.OTSModelInterface;
28  import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
29  import org.opentrafficsim.core.gtu.GTUType;
30  import org.opentrafficsim.core.gtu.following.GTUFollowingModel;
31  import org.opentrafficsim.core.gtu.following.IDM;
32  import org.opentrafficsim.core.gtu.following.IDMPlus;
33  import org.opentrafficsim.core.gtu.lane.AbstractLaneBasedGTU;
34  import org.opentrafficsim.core.network.LateralDirectionality;
35  import org.opentrafficsim.core.network.NetworkException;
36  import org.opentrafficsim.core.network.factory.LaneFactory;
37  import org.opentrafficsim.core.network.factory.Node;
38  import org.opentrafficsim.core.network.lane.Lane;
39  import org.opentrafficsim.core.network.lane.LaneLocation;
40  import org.opentrafficsim.core.network.lane.LaneType;
41  import org.opentrafficsim.core.unit.AccelerationUnit;
42  import org.opentrafficsim.core.unit.LengthUnit;
43  import org.opentrafficsim.core.unit.SpeedUnit;
44  import org.opentrafficsim.core.unit.TimeUnit;
45  import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar;
46  import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar.Abs;
47  import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar.Rel;
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.simulationengine.AbstractProperty;
56  import org.opentrafficsim.simulationengine.BooleanProperty;
57  import org.opentrafficsim.simulationengine.CompoundProperty;
58  import org.opentrafficsim.simulationengine.ContinuousProperty;
59  import org.opentrafficsim.simulationengine.ControlPanel;
60  import org.opentrafficsim.simulationengine.IDMPropertySet;
61  import org.opentrafficsim.simulationengine.IntegerProperty;
62  import org.opentrafficsim.simulationengine.ProbabilityDistributionProperty;
63  import org.opentrafficsim.simulationengine.PropertyException;
64  import org.opentrafficsim.simulationengine.SelectionProperty;
65  import org.opentrafficsim.simulationengine.SimpleSimulator;
66  import org.opentrafficsim.simulationengine.SimulatorFrame;
67  import org.opentrafficsim.simulationengine.WrappableSimulation;
68  
69  import com.vividsolutions.jts.geom.Coordinate;
70  
71  /**
72   * Circular road simulation demo.
73   * <p>
74   * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
75   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
76   * <p>
77   * @version 21 nov. 2014 <br>
78   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
79   */
80  public class CircularRoad implements WrappableSimulation
81  {
82      /** The properties exhibited by this simulation. */
83      private ArrayList<AbstractProperty<?>> properties = new ArrayList<AbstractProperty<?>>();
84  
85      /** Create a CircularRoad simulation. */
86      public CircularRoad()
87      {
88          this.properties.add(new SelectionProperty("Lane changing",
89              "<html>The lane change strategies vary in politeness.<br />"
90                  + "Two types are implemented:<ul><li>Egoistic (looks only at personal gain).</li>"
91                  + "<li>Altruistic (assigns effect on new and current follower the same weight as "
92                  + "the personal gain).</html>", new String[] {"Egoistic", "Altruistic"}, 0, false, 500));
93          this.properties.add(new IntegerProperty("Track length", "Circumference of the track", 2000, 500, 6000,
94              "Track length %dm", false, 10));
95          this.properties.add(new ContinuousProperty("Mean density", "Number of vehicles per km", 40.0, 5.0, 45.0,
96              "Density %.1f veh/km", false, 11));
97          this.properties.add(new ContinuousProperty("Density variability", "Variability of the number of vehicles per km",
98              0.0, 0.0, 1.0, "%.1f", false, 12));
99          ArrayList<AbstractProperty<?>> outputProperties = new ArrayList<AbstractProperty<?>>();
100         for (int lane = 1; lane <= 2; lane++)
101         {
102             String laneId = String.format("Lane %d ", lane);
103             outputProperties.add(new BooleanProperty(laneId + "Density", laneId + "Density contour plot", true, false, 0));
104             outputProperties.add(new BooleanProperty(laneId + "Flow", laneId + "Flow contour plot", true, false, 1));
105             outputProperties.add(new BooleanProperty(laneId + "Speed", laneId + "Speed contour plot", true, false, 2));
106             outputProperties.add(new BooleanProperty(laneId + "Acceleration", laneId + "Acceleration contour plot", true,
107                 false, 3));
108             outputProperties.add(new BooleanProperty(laneId + "Trajectories", laneId + "Trajectory (time/distance) diagram",
109                 true, false, 4));
110         }
111         this.properties.add(new CompoundProperty("Output", "Select the graphical output", outputProperties, true, 1000));
112     }
113 
114     /**
115      * Main program.
116      * @param args String[]; the command line arguments (not used)
117      * @throws SimRuntimeException
118      * @throws RemoteException
119      */
120     public static void main(final String[] args) throws RemoteException, SimRuntimeException
121     {
122         SwingUtilities.invokeLater(new Runnable()
123         {
124             @Override
125             public void run()
126             {
127                 try
128                 {
129                     CircularRoad circularRoad = new CircularRoad();
130                     ArrayList<AbstractProperty<?>> properties = circularRoad.getProperties();
131                     try
132                     {
133                         properties.add(new ProbabilityDistributionProperty("Traffic composition",
134                             "<html>Mix of passenger cars and trucks</html>", new String[] {"passenger car", "truck"},
135                             new Double[] {0.8, 0.2}, false, 10));
136                     }
137                     catch (PropertyException exception)
138                     {
139                         exception.printStackTrace();
140                     }
141                     properties.add(new SelectionProperty("Car following model", "<html>The car following model determines "
142                         + "the acceleration that a vehicle will make taking into account "
143                         + "nearby vehicles, infrastructural restrictions (e.g. speed limit, "
144                         + "curvature of the road) capabilities of the vehicle and personality " + "of the driver.</html>",
145                         new String[] {"IDM", "IDM+"}, 1, false, 1));
146                     properties.add(IDMPropertySet.makeIDMPropertySet("Car", new DoubleScalar.Abs<AccelerationUnit>(1.0,
147                         AccelerationUnit.METER_PER_SECOND_2), new DoubleScalar.Abs<AccelerationUnit>(1.5,
148                         AccelerationUnit.METER_PER_SECOND_2), new DoubleScalar.Rel<LengthUnit>(2.0, LengthUnit.METER),
149                         new DoubleScalar.Rel<TimeUnit>(1.0, TimeUnit.SECOND), 2));
150                     properties.add(IDMPropertySet.makeIDMPropertySet("Truck", new DoubleScalar.Abs<AccelerationUnit>(0.5,
151                         AccelerationUnit.METER_PER_SECOND_2), new DoubleScalar.Abs<AccelerationUnit>(1.25,
152                         AccelerationUnit.METER_PER_SECOND_2), new DoubleScalar.Rel<LengthUnit>(2.0, LengthUnit.METER),
153                         new DoubleScalar.Rel<TimeUnit>(1.0, TimeUnit.SECOND), 3));
154                     new SimulatorFrame("Circular Road animation", circularRoad.buildSimulator(properties).getPanel());
155                 }
156                 catch (RemoteException | SimRuntimeException exception)
157                 {
158                     exception.printStackTrace();
159                 }
160             }
161         });
162     }
163 
164     /**
165      * Create the simulation.
166      * @return SimpleSimulator; the simulation
167      * @throws RemoteException on communications failure
168      * @throws SimRuntimeException on ???
169      */
170     public SimpleSimulator buildSimulator(ArrayList<AbstractProperty<?>> userModifiedProperties) throws RemoteException,
171         SimRuntimeException
172     {
173         RoadSimulationModel model = new RoadSimulationModel(userModifiedProperties);
174         final SimpleSimulator result =
175             new SimpleSimulator(new OTSSimTimeDouble(new DoubleScalar.Abs<TimeUnit>(0.0, TimeUnit.SECOND)),
176                 new DoubleScalar.Rel<TimeUnit>(0.0, TimeUnit.SECOND),
177                 new DoubleScalar.Rel<TimeUnit>(3600.0, TimeUnit.SECOND), model, new Rectangle2D.Double(-1000, -1000, 2000,
178                     2000));
179         new ControlPanel(result);
180 
181         // Make the tab with the plots
182         AbstractProperty<?> output = new CompoundProperty("", "", this.properties, false, 0).findByShortName("Output");
183         if (null == output)
184         {
185             throw new Error("Cannot find output properties");
186         }
187         ArrayList<BooleanProperty> graphs = new ArrayList<BooleanProperty>();
188         if (output instanceof CompoundProperty)
189         {
190             CompoundProperty outputProperties = (CompoundProperty) output;
191             for (AbstractProperty<?> ap : outputProperties.getValue())
192             {
193                 if (ap instanceof BooleanProperty)
194                 {
195                     BooleanProperty bp = (BooleanProperty) ap;
196                     if (bp.getValue())
197                     {
198                         graphs.add(bp);
199                     }
200                 }
201             }
202         }
203         else
204         {
205             throw new Error("output properties should be compound");
206         }
207         int graphCount = graphs.size();
208         int columns = (int) Math.ceil(Math.sqrt(graphCount));
209         int rows = 0 == columns ? 0 : (int) Math.ceil(graphCount * 1.0 / columns);
210         TablePanel charts = new TablePanel(columns, rows);
211         result.getPanel().getTabbedPane().addTab("statistics", charts);
212 
213         for (int i = 0; i < graphCount; i++)
214         {
215             String graphName = graphs.get(i).getShortName();
216             Container container = null;
217             LaneBasedGTUSampler graph;
218             int pos = graphName.indexOf(' ') + 1;
219             String laneNumberText = graphName.substring(pos, pos + 1);
220             int lane = Integer.parseInt(laneNumberText) - 1;
221 
222             if (graphName.contains("Trajectories"))
223             {
224                 TrajectoryPlot tp =
225                     new TrajectoryPlot(graphName, new DoubleScalar.Rel<TimeUnit>(0.5, TimeUnit.SECOND), model
226                         .getMinimumDistance(), model.lanes[lane].getLength());
227                 tp.setTitle("Trajectory Graph");
228                 tp.setExtendedState(Frame.MAXIMIZED_BOTH);
229                 graph = tp;
230                 container = tp.getContentPane();
231             }
232             else
233             {
234                 ContourPlot cp;
235                 if (graphName.contains("Density"))
236                 {
237                     cp = new DensityContourPlot(graphName, model.getMinimumDistance(), model.lanes[lane].getLength());
238                     cp.setTitle("Density Contour Graph");
239                 }
240                 else if (graphName.contains("Speed"))
241                 {
242                     cp = new SpeedContourPlot(graphName, model.getMinimumDistance(), model.lanes[lane].getLength());
243                     cp.setTitle("Speed Contour Graph");
244                 }
245                 else if (graphName.contains("Flow"))
246                 {
247                     cp = new FlowContourPlot(graphName, model.getMinimumDistance(), model.lanes[lane].getLength());
248                     cp.setTitle("Flow Contour Graph");
249                 }
250                 else if (graphName.contains("Acceleration"))
251                 {
252                     cp = new AccelerationContourPlot(graphName, model.getMinimumDistance(), model.lanes[lane].getLength());
253                     cp.setTitle("Acceleration Contour Graph");
254                 }
255                 else
256                 {
257                     throw new Error("Unhandled type of contourplot: " + graphName);
258                 }
259                 graph = cp;
260                 container = cp.getContentPane();
261             }
262             // Add the container to the matrix
263             charts.setCell(container, i % columns, i / columns);
264             model.getPlots().get(lane).add(graph);
265         }
266         return result;
267     }
268 
269     /** {@inheritDoc} */
270     @Override
271     public String shortName()
272     {
273         return "Circular Road simulation";
274     }
275 
276     /** {@inheritDoc} */
277     @Override
278     public String description()
279     {
280         return "<html><h1>Circular Road simulation</h1>"
281             + "Vehicles are unequally distributed over a two lane ring road.<br />"
282             + "When simulation starts, all vehicles begin driving, some lane changes will occurr and some "
283             + "shockwaves should develop.<br />"
284             + "Trajectories and contourplots are generated during the simulation for both lanes.</html>";
285     }
286 
287     /** {@inheritDoc} */
288     @Override
289     public ArrayList<AbstractProperty<?>> getProperties()
290     {
291         return new ArrayList<AbstractProperty<?>>(this.properties);
292     }
293 
294 }
295 
296 /**
297  * Simulate traffic on a circular, two-lane road.
298  * <p>
299  * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
300  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
301  * <p>
302  * @version 21 nov. 2014 <br>
303  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
304  */
305 class RoadSimulationModel implements OTSModelInterface
306 {
307     /** */
308     private static final long serialVersionUID = 20141121L;
309 
310     /** the simulator. */
311     OTSDEVSSimulatorInterface simulator;
312 
313     /** Number of cars created. */
314     private int carsCreated = 0;
315 
316     /** the car following model, e.g. IDM Plus for cars. */
317     protected GTUFollowingModel carFollowingModelCars;
318 
319     /** the car following model, e.g. IDM Plus for trucks. */
320     protected GTUFollowingModel carFollowingModelTrucks;
321 
322     /** The probability that the next generated GTU is a passenger car. */
323     double carProbability;
324 
325     /** The lane change model. */
326     protected AbstractLaneChangeModel laneChangeModel;
327 
328     /** Cars in each lane. */
329     ArrayList<ArrayList<Car<Integer>>> cars;
330 
331     /** Minimum distance. */
332     private DoubleScalar.Rel<LengthUnit> minimumDistance = new DoubleScalar.Rel<LengthUnit>(0, LengthUnit.METER);
333 
334     /** The Lanes that contains the simulated Cars. */
335     Lane[] lanes;
336 
337     /** The speed limit. */
338     DoubleScalar.Abs<SpeedUnit> speedLimit = new DoubleScalar.Abs<SpeedUnit>(100, SpeedUnit.KM_PER_HOUR);
339 
340     /** The plots. */
341     private ArrayList<ArrayList<LaneBasedGTUSampler>> plots = new ArrayList<ArrayList<LaneBasedGTUSampler>>();
342 
343     /** User settable properties */
344     ArrayList<AbstractProperty<?>> properties = null;
345 
346     /** The random number generator used to decide what kind of GTU to generate. */
347     Random randomGenerator = new Random(12345);
348 
349     /**
350      * @param properties
351      */
352     public RoadSimulationModel(ArrayList<AbstractProperty<?>> properties)
353     {
354         this.properties = properties;
355     }
356 
357     /** {@inheritDoc} */
358     @Override
359     public void constructModel(SimulatorInterface<Abs<TimeUnit>, Rel<TimeUnit>, OTSSimTimeDouble> theSimulator)
360         throws SimRuntimeException, RemoteException
361     {
362         final int laneCount = 2;
363         this.cars = new ArrayList<ArrayList<Car<Integer>>>(laneCount);
364         for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
365         {
366             this.cars.add(new ArrayList<Car<Integer>>());
367             this.plots.add(new ArrayList<LaneBasedGTUSampler>());
368         }
369         this.simulator = (OTSDEVSSimulatorInterface) theSimulator;
370         double radius = 6000 / 2 / Math.PI;
371         double headway = 40;
372         double headwayVariability = 0;
373         try
374         {
375             String carFollowingModelName = null;
376             CompoundProperty propertyContainer = new CompoundProperty("", "", this.properties, false, 0);
377             AbstractProperty<?> cfmp = propertyContainer.findByShortName("Car following model");
378             if (null == cfmp)
379             {
380                 throw new Error("Cannot find \"Car following model\" property");
381             }
382             if (cfmp instanceof SelectionProperty)
383             {
384                 carFollowingModelName = ((SelectionProperty) cfmp).getValue();
385             }
386             else
387             {
388                 throw new Error("\"Car following model\" property has wrong type");
389             }
390             Iterator<AbstractProperty<ArrayList<AbstractProperty<?>>>> iterator =
391                 new CompoundProperty("", "", this.properties, false, 0).iterator();
392             while (iterator.hasNext())
393             {
394                 AbstractProperty<?> ap = iterator.next();
395                 if (ap instanceof SelectionProperty)
396                 {
397                     SelectionProperty sp = (SelectionProperty) ap;
398                     if ("Car following model".equals(sp.getShortName()))
399                     {
400                         carFollowingModelName = sp.getValue();
401                     }
402                     else if ("Lane changing".equals(sp.getShortName()))
403                     {
404                         String strategyName = sp.getValue();
405                         if ("Egoistic".equals(strategyName))
406                         {
407                             this.laneChangeModel = new Egoistic();
408                         }
409                         else if ("Altruistic".equals(strategyName))
410                         {
411                             this.laneChangeModel = new Altruistic();
412                         }
413                         else
414                         {
415                             throw new Error("Lane changing " + strategyName + " not implemented");
416                         }
417                     }
418                 }
419                 else if (ap instanceof ProbabilityDistributionProperty)
420                 {
421                     ProbabilityDistributionProperty pdp = (ProbabilityDistributionProperty) ap;
422                     if (ap.getShortName().equals("Traffic composition"))
423                     {
424                         this.carProbability = pdp.getValue()[0];
425                     }
426                 }
427                 else if (ap instanceof IntegerProperty)
428                 {
429                     IntegerProperty ip = (IntegerProperty) ap;
430                     if ("Track length".equals(ip.getShortName()))
431                     {
432                         radius = ip.getValue() / 2 / Math.PI;
433                     }
434                 }
435                 else if (ap instanceof ContinuousProperty)
436                 {
437                     ContinuousProperty cp = (ContinuousProperty) ap;
438                     if (cp.getShortName().equals("Mean density"))
439                     {
440                         headway = 1000 / cp.getValue();
441                     }
442                     if (cp.getShortName().equals("Density variability"))
443                     {
444                         headwayVariability = cp.getValue();
445                     }
446                 }
447                 else if (ap instanceof CompoundProperty)
448                 {
449                     CompoundProperty cp = (CompoundProperty) ap;
450                     if (ap.getShortName().equals("Output"))
451                     {
452                         continue; // Output settings are handled elsewhere
453                     }
454                     if (ap.getShortName().contains("IDM"))
455                     {
456                         // System.out.println("Car following model name appears to be " + ap.getShortName());
457                         DoubleScalar.Abs<AccelerationUnit> a = IDMPropertySet.getA(cp);
458                         DoubleScalar.Abs<AccelerationUnit> b = IDMPropertySet.getB(cp);
459                         DoubleScalar.Rel<LengthUnit> s0 = IDMPropertySet.getS0(cp);
460                         DoubleScalar.Rel<TimeUnit> tSafe = IDMPropertySet.getTSafe(cp);
461                         GTUFollowingModel gtuFollowingModel = null;
462                         if (carFollowingModelName.equals("IDM"))
463                         {
464                             gtuFollowingModel = new IDM(a, b, s0, tSafe, 1.0);
465                         }
466                         else if (carFollowingModelName.equals("IDM+"))
467                         {
468                             gtuFollowingModel = new IDMPlus(a, b, s0, tSafe, 1.0);
469                         }
470                         else
471                         {
472                             throw new Error("Unknown gtu following model: " + carFollowingModelName);
473                         }
474                         if (ap.getShortName().contains(" Car "))
475                         {
476                             this.carFollowingModelCars = gtuFollowingModel;
477                         }
478                         else if (ap.getShortName().contains(" Truck "))
479                         {
480                             this.carFollowingModelTrucks = gtuFollowingModel;
481                         }
482                         else
483                         {
484                             throw new Error("Cannot determine gtu type for " + ap.getShortName());
485                         }
486                         /*
487                          * System.out.println("Created " + carFollowingModelName + " for " + p.getShortName());
488                          * System.out.println("a: " + a); System.out.println("b: " + b); System.out.println("s0: " + s0);
489                          * System.out.println("tSafe: " + tSafe);
490                          */
491                     }
492                 }
493             }
494             GTUType<String> gtuType = new GTUType<String>("car");
495             LaneType<String> laneType = new LaneType<String>("CarLane");
496             laneType.addPermeability(gtuType);
497             Node startEnd = new Node("Start/End", new Coordinate(radius, 0, 0));
498             Coordinate[] intermediateCoordinates = new Coordinate[255];
499             for (int i = 0; i < intermediateCoordinates.length; i++)
500             {
501                 double angle = 2 * Math.PI * (1 + i) / (1 + intermediateCoordinates.length);
502                 intermediateCoordinates[i] = new Coordinate(radius * Math.cos(angle), radius * Math.sin(angle), 0);
503             }
504             this.lanes =
505                 LaneFactory.makeMultiLane("Circular Link with " + laneCount + " lanes", startEnd, startEnd,
506                     intermediateCoordinates, laneCount, laneType, this.simulator);
507             // Put the (not very evenly spaced) cars on the track
508             double variability = (headway - 20) * headwayVariability;
509             System.out.println("headway is " + headway + " variability limit is " + variability);
510             Random random = new Random(12345);
511             for (int laneIndex = 0; laneIndex < this.lanes.length; laneIndex++)
512             {
513                 double trackLength = this.lanes[laneIndex].getLength().getSI();
514                 for (double pos = 0; pos <= trackLength - headway - variability;)
515                 {
516                     // Actual headway is uniformly distributed around headway
517                     double actualHeadway = headway + (random.nextDouble() * 2 - 1) * variability;
518                     generateCar(new DoubleScalar.Rel<LengthUnit>(pos, LengthUnit.METER), laneIndex, gtuType);
519                     /*
520                      * if (pos > trackLength / 4 && pos < 3 * trackLength / 4) { generateCar(new
521                      * DoubleScalar.Rel<LengthUnit>(pos + headway / 2, LengthUnit.METER), laneIndex, gtuType); }
522                      */
523                     pos += actualHeadway;
524                 }
525             }
526             // Schedule regular updates of the graph
527             this.simulator.scheduleEventAbs(new DoubleScalar.Abs<TimeUnit>(9.999, TimeUnit.SECOND), this, this,
528                 "drawGraphs", null);
529             checkOrdering(RoadSimulationModel.this.cars.get(0));
530             checkOrdering(RoadSimulationModel.this.cars.get(1));
531         }
532         catch (RemoteException | SimRuntimeException | NamingException | NetworkException exception)
533         {
534             exception.printStackTrace();
535         }
536     }
537 
538     /**
539      * Add one movement step of one Car to all plots.
540      * @param car Car
541      * @throws NetworkException when car not in lane
542      * @throws RemoteException on communications failure
543      */
544     protected final void addToPlots(final Car<?> car, int lane) throws NetworkException, RemoteException
545     {
546         for (LaneBasedGTUSampler plot : this.plots.get(lane))
547         {
548             plot.addData(car);
549         }
550     }
551 
552     /**
553      * Notify the contour plots that the underlying data has changed.
554      */
555     protected final void drawGraphs()
556     {
557         for (ArrayList<LaneBasedGTUSampler> lanePlots : this.plots)
558         {
559             for (LaneBasedGTUSampler plot : lanePlots)
560             {
561                 plot.reGraph();
562             }
563         }
564         // Re schedule this method
565         try
566         {
567             this.simulator.scheduleEventAbs(new DoubleScalar.Abs<TimeUnit>(
568                 this.simulator.getSimulatorTime().get().getSI() + 10, TimeUnit.SECOND), this, this, "drawGraphs", null);
569         }
570         catch (RemoteException | SimRuntimeException exception)
571         {
572             exception.printStackTrace();
573         }
574 
575     }
576 
577     /**
578      * Generate cars at a fixed rate (implemented by re-scheduling this method).
579      * @throws NamingException on ???
580      * @throws SimRuntimeException
581      * @throws NetworkException
582      */
583     protected final void generateCar(DoubleScalar.Rel<LengthUnit> initialPosition, int laneIndex, GTUType<String> gtuType)
584         throws NamingException, NetworkException, SimRuntimeException
585     {
586         boolean generateTruck = this.randomGenerator.nextDouble() > this.carProbability;
587         DoubleScalar.Abs<SpeedUnit> initialSpeed = new DoubleScalar.Abs<SpeedUnit>(0, SpeedUnit.KM_PER_HOUR);
588         Map<Lane, DoubleScalar.Rel<LengthUnit>> initialPositions = new HashMap<Lane, DoubleScalar.Rel<LengthUnit>>();
589         initialPositions.put(this.lanes[laneIndex], initialPosition);
590         try
591         {
592             DoubleScalar.Rel<LengthUnit> vehicleLength =
593                 new DoubleScalar.Rel<LengthUnit>(generateTruck ? 15 : 4, LengthUnit.METER);
594             IDMCar car =
595                 new IDMCar(++this.carsCreated, gtuType, this.simulator, generateTruck ? this.carFollowingModelTrucks
596                     : this.carFollowingModelCars, vehicleLength, this.simulator.getSimulatorTime().get(), initialPositions,
597                     initialSpeed);
598             this.cars.get(laneIndex).add(car);
599         }
600         catch (RemoteException exception)
601         {
602             exception.printStackTrace();
603         }
604     }
605 
606     /** {@inheritDoc} */
607     @Override
608     public SimulatorInterface<Abs<TimeUnit>, Rel<TimeUnit>, OTSSimTimeDouble> getSimulator() throws RemoteException
609     {
610         return null;
611     }
612 
613     /**
614      * @return plots
615      */
616     public final ArrayList<ArrayList<LaneBasedGTUSampler>> getPlots()
617     {
618         return this.plots;
619     }
620 
621     /**
622      * @return minimumDistance
623      */
624     public final DoubleScalar.Rel<LengthUnit> getMinimumDistance()
625     {
626         return this.minimumDistance;
627     }
628 
629     /**
630      * Inner class IDMCar.
631      */
632     protected class IDMCar extends Car<Integer>
633     {
634         /** */
635         private static final long serialVersionUID = 20141030L;
636 
637         /**
638          * Create a new IDMCar.
639          * @param id integer; the id of the new IDMCar
640          * @param gtuType GTUType&lt;String&gt;; the type of the GTU
641          * @param simulator OTSDEVSSimulator; the simulator that runs the new IDMCar
642          * @param carFollowingModel CarFollowingModel; the car following model of the new IDMCar
643          * @param vehicleLength DoubleScalar.Rel&lt;LengthUnit&gt;; the length of the new IDMCar
644          * @param initialTime DoubleScalar.Abs&lt;TimeUnit&gt;; the time of first evaluation of the new IDMCar
645          * @param initialLongitudinalPositions Map&lt;Lane, DoubleScalar.Rel&lt;LengthUnit&gt;&gt;; the initial lane positions
646          *            of the new IDMCar
647          * @param initialSpeed DoubleScalar.Abs&lt;SpeedUnit&gt;; the initial speed of the new IDMCar
648          * @throws NamingException on ???
649          * @throws RemoteException on communication failure
650          * @throws SimRuntimeException
651          * @throws NetworkException
652          */
653         public IDMCar(final int id, GTUType<String> gtuType, final OTSDEVSSimulatorInterface simulator,
654             final GTUFollowingModel carFollowingModel, DoubleScalar.Rel<LengthUnit> vehicleLength,
655             final DoubleScalar.Abs<TimeUnit> initialTime,
656             final Map<Lane, DoubleScalar.Rel<LengthUnit>> initialLongitudinalPositions,
657             final DoubleScalar.Abs<SpeedUnit> initialSpeed) throws RemoteException, NamingException, NetworkException,
658             SimRuntimeException
659         {
660             super(id, gtuType, carFollowingModel, initialLongitudinalPositions, initialSpeed, vehicleLength,
661                 new DoubleScalar.Rel<LengthUnit>(1.8, LengthUnit.METER), new DoubleScalar.Abs<SpeedUnit>(200,
662                     SpeedUnit.KM_PER_HOUR), simulator);
663             try
664             {
665                 if (id >= 0)
666                 {
667                     simulator.scheduleEventAbs(simulator.getSimulatorTime(), this, this, "move", null);
668                 }
669             }
670             catch (SimRuntimeException exception)
671             {
672                 exception.printStackTrace();
673             }
674             // System.out.println("Created IDMCar " + id);
675         }
676 
677         /**
678          * Determine the movement of this car.
679          * @throws RemoteException RemoteException
680          * @throws NamingException on ???
681          * @throws NetworkException on network inconsistency
682          * @throws SimRuntimeException on ???
683          */
684         protected final void move() throws RemoteException, NamingException, NetworkException, SimRuntimeException
685         {
686             wrap();
687             // System.out.println("move " + getId());
688             if (this.getId() < 0)
689             {
690                 return;
691             }
692             // FIXME: there should be a much easier way to obtain the leader; we should not have to maintain our own
693             // list
694             Lane lane = positions(getFront()).keySet().iterator().next();
695             int laneIndex = -1;
696             for (int i = 0; i < RoadSimulationModel.this.lanes.length; i++)
697             {
698                 if (lane == RoadSimulationModel.this.lanes[i])
699                 {
700                     laneIndex = i;
701                 }
702             }
703             if (laneIndex < 0)
704             {
705                 throw new Error("Cannot find lane of vehicle " + this);
706             }
707             DoubleScalar.Abs<TimeUnit> when = getSimulator().getSimulatorTime().get();
708             DoubleScalar.Rel<LengthUnit> longitudinalPosition = position(lane, getFront(), when);
709             double relativePosition = longitudinalPosition.getSI() / lane.getLength().getSI();
710             Collection<AbstractLaneBasedGTU<?>> sameLaneTraffic = carsInSpecifiedLane(laneIndex);
711             Collection<AbstractLaneBasedGTU<?>> leftLaneTraffic = carsInSpecifiedLane(laneIndex - 1);
712             Collection<AbstractLaneBasedGTU<?>> rightLaneTraffic = carsInSpecifiedLane(laneIndex + 1);
713             LaneChangeModel.LaneChangeModelResult lcmr =
714                 RoadSimulationModel.this.laneChangeModel.computeLaneChangeAndAcceleration(this, sameLaneTraffic,
715                     rightLaneTraffic, leftLaneTraffic, RoadSimulationModel.this.speedLimit,
716                     new DoubleScalar.Rel<AccelerationUnit>(0.3, AccelerationUnit.METER_PER_SECOND_2),
717                     new DoubleScalar.Rel<AccelerationUnit>(0.1, AccelerationUnit.METER_PER_SECOND_2),
718                     new DoubleScalar.Rel<AccelerationUnit>(-0.3, AccelerationUnit.METER_PER_SECOND_2));
719             // System.out.println("lane change result of " + this + ": " + lcmr);
720             if (lcmr.getLaneChange() != null)
721             {
722                 // Remember at what ratio on the old lane the vehicle was at the PREVIOUS time step
723                 // FIXME: the longitudinal positions map should not be editable from the outside...
724                 Map<Lane, Rel<LengthUnit>> longitudinalPositions = positions(getFront());
725                 DoubleScalar.Rel<LengthUnit> oldPosition = longitudinalPositions.get(lane);
726                 double oldRatio = oldPosition.getSI() / lane.getLength().getSI();
727                 // Remove vehicle from it's current lane
728                 RoadSimulationModel.this.cars.get(laneIndex).remove(this);
729                 // Figure out where to insert it in the target lane
730                 laneIndex += lcmr.getLaneChange().equals(LateralDirectionality.LEFT) ? -1 : +1;
731                 ArrayList<Car<Integer>> carsInLane = RoadSimulationModel.this.cars.get(laneIndex);
732                 lane = RoadSimulationModel.this.lanes[laneIndex];
733                 int pivot = pivot(relativePosition, carsInLane);
734                 // Insert vehicle
735                 // System.out.println("Inserting car " + this.getId() + " at position " + pivot);
736                 carsInLane.add(pivot, this);
737                 longitudinalPositions.clear();
738                 // Put the vehicle in the new lane at the ratio that it was at the PREVIOUS time step
739                 // The reason for this is that the vehicle is moved forward in setState below and setState requires
740                 // that the location has not yet been updated.
741                 longitudinalPositions.put(lane, new DoubleScalar.Rel<LengthUnit>(oldRatio * lane.getLength().getSI(),
742                     LengthUnit.METER));
743                 checkOrdering(carsInLane);
744             }
745             setState(lcmr.getGfmr());
746             checkOrdering(RoadSimulationModel.this.cars.get(0));
747             checkOrdering(RoadSimulationModel.this.cars.get(1));
748             // Add the movement of this Car to the contour plots
749             addToPlots(this, laneIndex);
750             // System.out.println("Moved " + this);
751             // Schedule the next evaluation of this car
752             getSimulator().scheduleEventRel(new DoubleScalar.Rel<TimeUnit>(0.5, TimeUnit.SECOND), this, this, "move", null);
753             // printList(0);
754             // printList(1);
755         }
756     }
757 
758     /**
759      * Wrap cars that are now beyond the length of their circular lane.
760      */
761     public void wrap()
762     {
763         DoubleScalar.Abs<TimeUnit> when = null;
764         try
765         {
766             when = RoadSimulationModel.this.simulator.getSimulatorTime().get();
767         }
768         catch (RemoteException exception1)
769         {
770             exception1.printStackTrace();
771         }
772         for (int laneIndex = 0; laneIndex < RoadSimulationModel.this.cars.size(); laneIndex++)
773         {
774             Lane lane = RoadSimulationModel.this.lanes[laneIndex];
775             ArrayList<Car<Integer>> carsInLane = RoadSimulationModel.this.cars.get(laneIndex);
776             int vehicleIndex = carsInLane.size() - 1;
777             if (vehicleIndex < 0)
778             {
779                 continue;
780             }
781             while (true)
782             {
783                 Car<Integer> car = carsInLane.get(vehicleIndex);
784                 LaneLocation ll = null;
785                 try
786                 {
787                     ll = new LaneLocation(lane, car.position(lane, car.getFront(), when));
788                 }
789                 catch (NetworkException exception)
790                 {
791                     exception.printStackTrace();
792                 }
793                 if (ll.getFractionalLongitudinalPosition() >= 1)
794                 {
795                     // Fix the RelativePositions
796                     // It is wrong that we can modify it, but for now we'll make use of that mistake...
797                     try
798                     {
799                         // FIXME: the longitudinal positions map should not be editable from the outside...
800                         Map<Lane, Rel<LengthUnit>> relativePositions = car.positions(car.getFront());
801                         double relativePosition = relativePositions.get(lane).getSI() / lane.getLength().getSI();
802                         // System.out.println("Wrapping car " + car.getId() + " in lane " + laneIndex +
803                         // " back to position 0");
804                         relativePositions.clear();
805                         relativePosition -= 1;
806                         relativePositions.put(lane, new DoubleScalar.Rel<LengthUnit>(relativePosition, LengthUnit.METER));
807                         carsInLane.remove(car);
808                         carsInLane.add(0, car);
809                         checkOrdering(carsInLane);
810                         addToPlots(car, laneIndex);
811                     }
812                     catch (RemoteException | NetworkException exception)
813                     {
814                         exception.printStackTrace();
815                     }
816                 }
817                 else
818                 {
819                     break;
820                 }
821             }
822         }
823     }
824 
825     /**
826      * Return the index of the car in the lane where a GTU at relativePosition should be inserted.
827      * @param relativePosition double; between 0 (begin of the lane) and 1 (end of the lane)
828      * @param carsInLane ArrayList&lt;AnimatedCar&gt;; the cars in the lane
829      * @return int
830      */
831     public int pivot(double relativePosition, ArrayList<Car<Integer>> carsInLane)
832     {
833         if (carsInLane.size() == 0)
834         {
835             return 0;
836         }
837         try
838         {
839             DoubleScalar.Abs<TimeUnit> when = carsInLane.get(0).getSimulator().getSimulatorTime().get();
840             Lane lane = carsInLane.get(0).positions(carsInLane.get(0).getFront()).keySet().iterator().next();
841             double laneLength = lane.getLength().getSI();
842             int result;
843             for (result = 0; result < carsInLane.size(); result++)
844             {
845                 Car<Integer> pivotCar = carsInLane.get(result);
846                 double pivotRelativePosition = pivotCar.position(lane, pivotCar.getFront(), when).getSI() / laneLength;
847                 if (pivotRelativePosition > relativePosition)
848                 {
849                     break;
850                 }
851             }
852             // System.out.println("pivot is " + result + " carsInLane.size is " + carsInLane.size());
853             return result;
854         }
855         catch (RemoteException | NetworkException exception)
856         {
857             exception.printStackTrace();
858         }
859         throw new Error("Oops");
860     }
861 
862     /**
863      * Find the leader and follower for a given relative position in the indicated lane.
864      * @param laneIndex int; the index of the lane
865      * @return ArrayList<AnimatedCar> containing the immediate leader and follower near relativePosition
866      */
867     @SuppressWarnings("unchecked")
868     Collection<AbstractLaneBasedGTU<?>> carsInSpecifiedLane(int laneIndex)
869     {
870         ArrayList<AbstractLaneBasedGTU<?>> result = new ArrayList<AbstractLaneBasedGTU<?>>();
871         if (laneIndex < 0 || laneIndex >= RoadSimulationModel.this.lanes.length)
872         {
873             return result;
874         }
875         Lane lane = RoadSimulationModel.this.lanes[laneIndex];
876         result.addAll(RoadSimulationModel.this.cars.get(laneIndex));
877         if (0 == result.size())
878         {
879             return result;
880         }
881         try
882         {
883             final double laneLength = lane.getLength().getSI();
884             // Add a wrapped copy of the last car at the beginning
885             Car<Integer> prototype = (Car<Integer>) result.get(result.size() - 1);
886             Map<Lane, DoubleScalar.Rel<LengthUnit>> initialPositions = new HashMap<Lane, DoubleScalar.Rel<LengthUnit>>();
887             DoubleScalar.Abs<TimeUnit> when = RoadSimulationModel.this.simulator.getSimulatorTime().get();
888             double position = prototype.position(lane, prototype.getFront(), when).getSI();
889             if (position > 0)
890             {
891                 position -= laneLength;
892             }
893             initialPositions.put(lane, new DoubleScalar.Rel<LengthUnit>(position, LengthUnit.METER));
894             // HACK FIXME (negative length trick)
895             IDMCar fakeFollower =
896                 new IDMCar(-10000 - prototype.getId(), (GTUType<String>) prototype.getGTUType(), prototype.getSimulator(),
897                     prototype.getGTUFollowingModel(), new DoubleScalar.Rel<LengthUnit>(-prototype.getLength().getSI(),
898                         LengthUnit.METER), when, initialPositions, prototype.getLongitudinalVelocity());
899             result.add(0, fakeFollower);
900             // Add a wrapped copy of the first (now second) car at the end
901             prototype = (Car<Integer>) result.get(1);
902             position = prototype.position(lane, prototype.getFront(), when).getSI();
903             if (position < laneLength)
904             {
905                 position += laneLength;
906             }
907             initialPositions = new HashMap<Lane, DoubleScalar.Rel<LengthUnit>>();
908             initialPositions.put(lane, new DoubleScalar.Rel<LengthUnit>(position, LengthUnit.METER));
909             // HACK FIXME (negative length trick)
910             IDMCar fakeLeader =
911                 new IDMCar(-20000 - prototype.getId(), (GTUType<String>) prototype.getGTUType(), prototype.getSimulator(),
912                     prototype.getGTUFollowingModel(), new DoubleScalar.Rel<LengthUnit>(-prototype.getLength().getSI(),
913                         LengthUnit.METER), when, initialPositions, prototype.getLongitudinalVelocity());
914             result.add(fakeLeader);
915         }
916         catch (RemoteException | NetworkException | NamingException | SimRuntimeException exception)
917         {
918             exception.printStackTrace();
919         }
920         return result;
921     }
922 
923     /**
924      * Sanity checks.
925      * @param list ArrayList&lt;AnimatedCar&gt;; the array of cars to check
926      */
927     public void checkOrdering(ArrayList<Car<Integer>> list)
928     {
929         if (list.size() == 0)
930         {
931             return;
932         }
933         try
934         {
935             Car<Integer> first = list.get(0);
936             Lane lane = first.positions(first.getFront()).keySet().iterator().next();
937             DoubleScalar.Abs<TimeUnit> when = first.getSimulator().getSimulatorTime().get();
938             double position = first.position(lane, first.getFront(), when).getSI();
939             for (int rank = 1; rank < list.size(); rank++)
940             {
941                 Car<Integer> other = list.get(rank);
942                 Lane otherLane = other.positions(other.getFront()).keySet().iterator().next();
943                 if (lane != otherLane)
944                 {
945                     printList(this.cars.indexOf(list));
946                     stopSimulator(first.getSimulator(), "cars are not all in the same lane");
947                 }
948                 double otherPosition = other.position(lane, other.getFront(), when).getSI();
949                 if (otherPosition <= position)
950                 {
951                     printList(this.cars.indexOf(list));
952                     stopSimulator(first.getSimulator(), "cars are not correctly ordered: " + first + " should be ahead of "
953                         + other);
954                 }
955                 first = other;
956                 position = otherPosition;
957             }
958         }
959         catch (RemoteException | NetworkException exception)
960         {
961             exception.printStackTrace();
962         }
963     }
964 
965     /**
966      * Print a list of cars on the console.
967      * @param laneIndex int; index of the lane whereof the cars must be printed
968      */
969     public void printList(int laneIndex)
970     {
971         ArrayList<Car<Integer>> list = this.cars.get(laneIndex);
972         for (int rank = 0; rank < list.size(); rank++)
973         {
974             Car<Integer> car = list.get(rank);
975             try
976             {
977                 double relativePosition =
978                     car.position(this.lanes[laneIndex], car.getFront(), this.simulator.getSimulatorTime().get()).getSI()
979                         / this.lanes[laneIndex].getLength().getSI();
980                 System.out.println(String.format("lane %d rank %2d relpos %7.5f: %s", laneIndex, rank, relativePosition, car
981                     .toString()));
982             }
983             catch (RemoteException | NetworkException exception)
984             {
985                 exception.printStackTrace();
986             }
987         }
988     }
989 
990     /**
991      * Stop simulation and throw an Error.
992      * @param theSimulator OTSDEVSSimulatorInterface; the simulator
993      * @param errorMessage String; the error message
994      */
995     public void stopSimulator(OTSDEVSSimulatorInterface theSimulator, String errorMessage)
996     {
997         System.out.println("Error: " + errorMessage);
998         try
999         {
1000             if (theSimulator.isRunning())
1001             {
1002                 theSimulator.stop();
1003             }
1004         }
1005         catch (RemoteException | SimRuntimeException exception)
1006         {
1007             exception.printStackTrace();
1008         }
1009         throw new Error(errorMessage);
1010     }
1011 
1012 }