View Javadoc
1   package org.opentrafficsim.demo.cacc;
2   
3   import java.awt.Color;
4   import java.io.BufferedWriter;
5   import java.io.IOException;
6   import java.io.OutputStreamWriter;
7   import java.rmi.RemoteException;
8   import java.util.ArrayList;
9   import java.util.Collections;
10  import java.util.LinkedHashSet;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Set;
14  
15  import org.djunits.unit.DurationUnit;
16  import org.djunits.unit.FrequencyUnit;
17  import org.djunits.unit.SpeedUnit;
18  import org.djunits.unit.TimeUnit;
19  import org.djunits.value.ValueRuntimeException;
20  import org.djunits.value.storage.StorageType;
21  import org.djunits.value.vdouble.scalar.Acceleration;
22  import org.djunits.value.vdouble.scalar.Direction;
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.djunits.value.vdouble.vector.FrequencyVector;
28  import org.djunits.value.vdouble.vector.TimeVector;
29  import org.djunits.value.vdouble.vector.base.DoubleVector;
30  import org.djunits.value.vfloat.scalar.FloatDuration;
31  import org.djutils.cli.Checkable;
32  import org.djutils.cli.CliException;
33  import org.djutils.cli.CliUtil;
34  import org.djutils.event.EventInterface;
35  import org.djutils.event.EventListenerInterface;
36  import org.opentrafficsim.base.compressedfiles.CompressionType;
37  import org.opentrafficsim.base.compressedfiles.Writer;
38  import org.opentrafficsim.core.animation.gtu.colorer.AccelerationGTUColorer;
39  import org.opentrafficsim.core.animation.gtu.colorer.IDGTUColorer;
40  import org.opentrafficsim.core.animation.gtu.colorer.SpeedGTUColorer;
41  import org.opentrafficsim.core.animation.gtu.colorer.SwitchableGTUColorer;
42  import org.opentrafficsim.core.distributions.ConstantGenerator;
43  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
44  import org.opentrafficsim.core.geometry.Bezier;
45  import org.opentrafficsim.core.geometry.OTSLine3D;
46  import org.opentrafficsim.core.geometry.OTSPoint3D;
47  import org.opentrafficsim.core.gtu.GTUDirectionality;
48  import org.opentrafficsim.core.gtu.GTUException;
49  import org.opentrafficsim.core.gtu.GTUType;
50  import org.opentrafficsim.core.gtu.TemplateGTUType;
51  import org.opentrafficsim.core.network.Link;
52  import org.opentrafficsim.core.network.LinkType;
53  import org.opentrafficsim.core.network.NetworkException;
54  import org.opentrafficsim.core.network.Node;
55  import org.opentrafficsim.core.network.OTSNode;
56  import org.opentrafficsim.core.parameters.ParameterFactory;
57  import org.opentrafficsim.core.parameters.ParameterFactoryByType;
58  import org.opentrafficsim.draw.factory.DefaultAnimationFactory;
59  import org.opentrafficsim.draw.graphs.ContourDataSource;
60  import org.opentrafficsim.draw.graphs.ContourPlotSpeed;
61  import org.opentrafficsim.draw.graphs.GraphPath;
62  import org.opentrafficsim.draw.graphs.road.GraphLaneUtil;
63  import org.opentrafficsim.kpi.sampling.KpiGtuDirectionality;
64  import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
65  import org.opentrafficsim.kpi.sampling.SamplingException;
66  import org.opentrafficsim.kpi.sampling.SpaceTimeRegion;
67  import org.opentrafficsim.kpi.sampling.Trajectory;
68  import org.opentrafficsim.kpi.sampling.TrajectoryGroup;
69  import org.opentrafficsim.kpi.sampling.meta.FilterDataGtuType;
70  import org.opentrafficsim.road.gtu.colorer.DesiredSpeedColorer;
71  import org.opentrafficsim.road.gtu.colorer.FixedColor;
72  import org.opentrafficsim.road.gtu.colorer.GTUTypeColorer;
73  import org.opentrafficsim.road.gtu.colorer.SplitColorer;
74  import org.opentrafficsim.road.gtu.colorer.SynchronizationColorer;
75  import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator;
76  import org.opentrafficsim.road.gtu.generator.Platoons;
77  import org.opentrafficsim.road.gtu.generator.od.DefaultGTUCharacteristicsGeneratorOD;
78  import org.opentrafficsim.road.gtu.generator.od.ODApplier;
79  import org.opentrafficsim.road.gtu.generator.od.ODApplier.GeneratorObjects;
80  import org.opentrafficsim.road.gtu.generator.od.ODOptions;
81  import org.opentrafficsim.road.gtu.generator.od.StrategicalPlannerFactorySupplierOD;
82  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
83  import org.opentrafficsim.road.gtu.lane.tactical.cacc.CaccController;
84  import org.opentrafficsim.road.gtu.lane.tactical.cacc.CaccControllerFactory;
85  import org.opentrafficsim.road.gtu.lane.tactical.cacc.CaccParameters;
86  import org.opentrafficsim.road.gtu.lane.tactical.cacc.CaccTacticalPlanner;
87  import org.opentrafficsim.road.gtu.lane.tactical.cacc.CaccTacticalPlannerFactory;
88  import org.opentrafficsim.road.gtu.lane.tactical.cacc.LongitudinalControllerFactory;
89  import org.opentrafficsim.road.gtu.lane.tactical.cacc.Platoon;
90  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusFactory;
91  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLMRSPerceptionFactory;
92  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRSFactory;
93  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
94  import org.opentrafficsim.road.gtu.strategical.od.Categorization;
95  import org.opentrafficsim.road.gtu.strategical.od.Category;
96  import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
97  import org.opentrafficsim.road.gtu.strategical.od.ODMatrix;
98  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlannerFactory;
99  import org.opentrafficsim.road.network.OTSRoadNetwork;
100 import org.opentrafficsim.road.network.lane.CrossSectionLink;
101 import org.opentrafficsim.road.network.lane.Lane;
102 import org.opentrafficsim.road.network.lane.LaneDirection;
103 import org.opentrafficsim.road.network.lane.LaneType;
104 import org.opentrafficsim.road.network.lane.OTSRoadNode;
105 import org.opentrafficsim.road.network.lane.Stripe;
106 import org.opentrafficsim.road.network.lane.Stripe.Permeable;
107 import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
108 import org.opentrafficsim.road.network.lane.object.sensor.Detector;
109 import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
110 import org.opentrafficsim.road.network.sampling.LaneData;
111 import org.opentrafficsim.road.network.sampling.RoadSampler;
112 import org.opentrafficsim.road.network.sampling.data.TimeToCollision;
113 import org.opentrafficsim.swing.graphs.SwingContourPlot;
114 import org.opentrafficsim.swing.gui.OTSSimulationApplication;
115 import org.opentrafficsim.swing.script.AbstractSimulationScript;
116 
117 import nl.tudelft.simulation.dsol.swing.gui.TablePanel;
118 import nl.tudelft.simulation.jstats.streams.StreamInterface;
119 import picocli.CommandLine;
120 import picocli.CommandLine.Option;
121 
122 /**
123  * <p>
124  * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
125  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
126  * <p>
127  * @version $Revision$, $LastChangedDate$, by $Author$, initial version 17 okt. 2018 <br>
128  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
129  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
130  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
131  */
132 public class CaccSimulation extends AbstractSimulationScript implements Checkable
133 {
134 
135     /** ... */
136     private static final long serialVersionUID = 1L;
137 
138     /** Sampler for statistics. */
139     private RoadSampler sampler;
140     
141     /** Time to collision data type. */
142     private final TimeToCollision ttc = new TimeToCollision();
143 
144     /** Meta GTU type. */
145     private final FilterDataGtuType metaGtu = new FilterDataGtuType();
146     
147     /** The CACC controller. */
148     private CaccController caccController;
149     
150     /** The GTU type with CACC. */
151     private GTUType caccGTUType;
152 
153     /** Space time regions to sample traffic on. */
154     private final List<SpaceTimeRegion> regions = new ArrayList<>();
155 
156     /** List of lane for the graphs. */
157     private List<Lane> graphLanes = new ArrayList<>();
158 
159     // Options
160     
161     /** Network name. */
162     @Option(names = "--nn", description = "Network name", defaultValue = "onramp")
163     private String networkName;
164 
165     /** Intensity factor. */
166     @Option(names = "--intensity", description = "Intensity factor", defaultValue = "1.00")
167     private double intensity;
168 
169     /** Penetration. */
170     @Option(names = "--penetration", description = "Fraction of vehicles equipped with CACC", defaultValue = "1.00")
171     private double penetration;
172     
173     /** Platoon size. */
174     @Option(names = "--platoon", description = "Platoon size", defaultValue = "3")
175     private int platoonSize;
176     
177     /** Headway. */
178     @Option(names = "--headway", description = "Platoon headway", defaultValue = "0.9")
179     private double headway;
180     
181     /** Synchronization. */
182     @Option(names = "--synchronization", description = "Synchronization", defaultValue = "0.0")
183     private double synchronization;
184     
185     /** Free flow speed of platoon vehicles. */
186     @Option(names = "--setspeed", description = "Free flow speed of platoon vehicles", defaultValue = "80 km/h")
187     private Speed setspeed;
188     
189     /** Time step. */
190     @Option(names = "--sd", description = "Simulation time", defaultValue = "5400s")
191     private Duration simulationDuration;
192     
193     /** Show graphs. */
194     @Option(names = "--graphs", description = "Show graphs", defaultValue = "true")
195     private boolean graphs;
196     
197     /** Number of lanes. */
198     @Option(names = "--nol", description = "Number of lanes", defaultValue = "2")
199     private int numberOfLanes;
200     
201     /** Controller. */
202     @Option(names = "--controller", description = "Controller", defaultValue = "CACC")
203     private String controller;
204     
205     /** Output file for detections (?). */
206     @Option(names = "--outputFileDets", description = "Output file for detections", 
207             defaultValue = "C:\\\\Temp\\\\Journal-Platoon\\\\DetsDefault.csv")
208     private String outputFileDets;
209     
210     /** Output file for trajectories (?). */
211     @Option(names = "--outputFileTraj", description = "Output file for trajectories",
212             defaultValue = "C:\\\\Temp\\\\Journal-Platoon\\\\GenDefault")
213     private String outputFileTraj;
214     
215     /** Output file for Generators (?). */
216     @Option(names = "--outputFileGen", description = "Output file for generators", 
217             defaultValue = "C:\\Temp\\Journal-Platoon\\GenDefault")
218     private String outputFileGen;
219     
220     /** Equilibrium distance. */
221     @Option(names = "--startSpacing", description = "Gross equilibrium distance at 25m/s with s0=3m, l=12m and T=0.3s",
222             defaultValue = "0.5")
223     private double startSpacing;
224     
225     /**
226      * @param properties x
227      * @throws CliException x 
228      * @throws NoSuchFieldException x 
229      */
230     protected CaccSimulation(final String[] properties) throws NoSuchFieldException, CliException
231     {
232         super("Truck CACC", "Simulation of CACC trucks");
233         CommandLine cmd = new CommandLine(this);
234         CliUtil.changeOptionDefault(AbstractSimulationScript.class, "seed", "5");
235         CliUtil.execute(cmd, properties);
236     }
237 
238     /**
239      * Main method that creates and starts a simulation.
240      * @param args String[]; command line arguments
241      * @throws Exception ...
242      */
243     public static void main(final String[] args) throws Exception
244     {
245         new CaccSimulation(args).start();
246     }
247 
248     /** {@inheritDoc} */
249     @Override
250     protected OTSRoadNetwork setupSimulation(final OTSSimulatorInterface sim) throws Exception
251     {
252         OTSRoadNetwork network = new OTSRoadNetwork(this.networkName, true, sim);
253         GTUType carGTUType = network.getGtuType(GTUType.DEFAULTS.CAR);
254         GTUType truckGTUType = network.getGtuType(GTUType.DEFAULTS.TRUCK);
255         this.caccGTUType = new GTUType("CACC", truckGTUType);
256         LinkType freewayLinkType = network.getLinkType(LinkType.DEFAULTS.FREEWAY);
257         LaneType freewayLaneType = network.getLaneType(LaneType.DEFAULTS.FREEWAY);
258         GTUType vehicle = network.getGtuType(GTUType.DEFAULTS.VEHICLE);
259         this.caccController = new CaccController();
260         this.caccController.setCACCGTUType(this.caccGTUType);
261         setGtuColorer(new SwitchableGTUColorer(0, new FixedColor(Color.BLUE, "Blue"),
262                 new GTUTypeColorer().add(carGTUType, Color.BLUE).add(truckGTUType, Color.RED)
263                         .add(truckGTUType, Color.GREEN),
264                 new IDGTUColorer(), new SpeedGTUColorer(new Speed(150, SpeedUnit.KM_PER_HOUR)),
265                 new DesiredSpeedColorer(new Speed(50, SpeedUnit.KM_PER_HOUR), new Speed(150, SpeedUnit.KM_PER_HOUR)),
266                 new AccelerationGTUColorer(Acceleration.instantiateSI(-6.0), Acceleration.instantiateSI(2)), new SplitColorer(),
267                 new SynchronizationColorer()));
268 
269         // Factory for settable parameters
270         ParameterFactoryByType parameters = new ParameterFactoryByType();
271         // Parameters
272         parameters.addParameter(CaccParameters.T_SYSTEM_CACC, new Duration(this.headway, DurationUnit.SI));
273         parameters.addParameter(CaccParameters.A_REDUCED, Acceleration.instantiateSI(this.synchronization));
274         parameters.addParameter(CaccParameters.SET_SPEED, this.setspeed);
275 
276         CaccControllerFactory longitudinalControllerFactory;
277         String controllerName = this.controller;
278         if (controllerName.equals("CACC"))
279         {
280             longitudinalControllerFactory = new CaccControllerFactory();
281         }
282         else
283         {
284             throw new RuntimeException("Controller " + controllerName + " not supported.");
285         }
286 
287         Length laneWidth = Length.instantiateSI(3.5);
288         Length stripeWidth = Length.instantiateSI(0.2);
289         Map<String, GeneratorObjects> odApplierOutput;
290 
291         String platoonDetector1;
292 
293         if (this.networkName.equals("onramp"))
294         {
295             // points
296             OTSPoint3D pointA = new OTSPoint3D(0, 0);
297             OTSPoint3D pointB = new OTSPoint3D(2000, 0); // 700 meters toerit
298             OTSPoint3D pointC = new OTSPoint3D(2330, 0); // 330 meters onramp
299             OTSPoint3D pointD = new OTSPoint3D(3330, 0);
300             OTSPoint3D pointE = new OTSPoint3D(1300, -40);
301 
302             // nodes
303             OTSRoadNode nodeA = new OTSRoadNode(network, "A", pointA, Direction.ZERO);
304             OTSRoadNode nodeB = new OTSRoadNode(network, "B", pointB, Direction.ZERO);
305             OTSRoadNode nodeC = new OTSRoadNode(network, "C", pointC, Direction.ZERO);
306             OTSRoadNode nodeD = new OTSRoadNode(network, "D", pointD, Direction.ZERO);
307             OTSRoadNode nodeE = new OTSRoadNode(network, "E", pointE, Direction.ZERO);
308 
309             // links
310             CrossSectionLink linkAB = new CrossSectionLink(network, "AB", nodeA, nodeB, freewayLinkType,
311                     new OTSLine3D(pointA, pointB), LaneKeepingPolicy.KEEPRIGHT);
312             CrossSectionLink linkBC = new CrossSectionLink(network, "BC", nodeB, nodeC, freewayLinkType,
313                     new OTSLine3D(pointB, pointC), LaneKeepingPolicy.KEEPRIGHT);
314             CrossSectionLink linkCD = new CrossSectionLink(network, "CD", nodeC, nodeD, freewayLinkType,
315                     new OTSLine3D(pointC, pointD), LaneKeepingPolicy.KEEPRIGHT);
316             CrossSectionLink linkEB = new CrossSectionLink(network, "EB", nodeE, nodeB, freewayLinkType,
317                     Bezier.cubic(nodeE.getLocation(), nodeB.getLocation()), LaneKeepingPolicy.KEEPRIGHT);
318 
319             // lanes and stripes
320             int n = this.numberOfLanes;
321             // List<Lane> originLanes = new ArrayList<>();
322             for (int i = 0; i < n; i++)
323             {
324                 for (CrossSectionLink link : new CrossSectionLink[] { linkAB, linkBC, linkCD })
325                 {
326                     Lane lane = new Lane(link, "Lane " + (i + 1), laneWidth.times((0.5 + i)), laneWidth, freewayLaneType,
327                             new Speed(100, SpeedUnit.KM_PER_HOUR));
328                     Length offset = laneWidth.times(i + 1.0);
329                     Stripe stripe = new Stripe(link, offset, offset, stripeWidth);
330                     if (i < n - 1)
331                     {
332                         if (lane.getParentLink().getId().equals("BC"))
333                         {
334                             // stripe.addPermeability(GTUType.VEHICLE, Permeable.LEFT);
335                             stripe.addPermeability(vehicle, Permeable.BOTH);
336                         }
337                         else
338                         {
339                             stripe.addPermeability(vehicle, Permeable.BOTH);
340                         }
341                     }
342                     if (lane.getParentLink().getId().equals("BC"))
343                     {
344                         // new Detector(lane.getFullId(), lane, Length.createSI(150.0), sim);
345                         new Detector(lane.getFullId(), lane, Length.instantiateSI(330.0), sim);
346                     }
347                     // sink sensors
348                     if (lane.getParentLink().getId().equals("CD"))
349                     {
350                         new SinkSensor(lane, lane.getLength().minus(Length.instantiateSI(100.0)), GTUDirectionality.DIR_PLUS,
351                                 sim);
352                         // detectors 100m after on ramp
353                         // new Detector(lane.getFullId(), lane, Length.createSI(0.0), sim); // id equal to lane, may be
354                         // different
355                     }
356                     if (lane.getParentLink().getId().equals("AB"))
357                     {
358                         // originLanes.add(lane);
359                         this.graphLanes.add(lane);
360                     }
361                 }
362             }
363             new Stripe(linkAB, Length.ZERO, Length.ZERO, stripeWidth);
364             Stripe stripe = new Stripe(linkBC, Length.ZERO, Length.ZERO, stripeWidth);
365             stripe.addPermeability(vehicle, Permeable.LEFT);
366             new Stripe(linkCD, Length.ZERO, Length.ZERO, stripeWidth);
367             new Lane(linkBC, "Acceleration lane", laneWidth.times(-0.5), laneWidth, freewayLaneType,
368                     new Speed(100, SpeedUnit.KM_PER_HOUR));
369             Lane onramp = new Lane(linkEB, "Onramp", laneWidth.times(-0.5), laneWidth, freewayLaneType,
370                     new Speed(100, SpeedUnit.KM_PER_HOUR));
371             new Stripe(linkEB, Length.ZERO, Length.ZERO, stripeWidth);
372             new Stripe(linkEB, laneWidth.neg(), laneWidth.neg(), stripeWidth);
373             new Stripe(linkBC, laneWidth.neg(), laneWidth.neg(), stripeWidth);
374 
375             // Detector on onramp
376             // new Detector("Acceleration lane", accel, Length.createSI(200.0), sim);
377 
378             // OD without demand
379             List<OTSNode> origins = new ArrayList<>();
380             origins.add(nodeA);
381             origins.add(nodeE);
382             List<OTSNode> destinations = new ArrayList<>();
383             destinations.add(nodeD);
384             TimeVector timeVector = DoubleVector.instantiate(new double[] { 0.0, 0.25, 0.50, 0.75, 1.0, 1.25, 1.50, 1.75, 2.0 },
385                     TimeUnit.BASE_HOUR, StorageType.DENSE);
386             Interpolation interpolation = Interpolation.LINEAR; // or STEPWISE
387             Categorization categorization = new Categorization("CACC", GTUType.class, Lane.class);
388             // Category carCategory1 = new Category(categorization, carGTUType, originLanes.get(1));
389             // Category carCategory0 = new Category(categorization, carGTUType, originLanes.get(0));
390             Category carCategory1 = new Category(categorization, carGTUType, this.graphLanes.get(1));
391             Category carCategory0 = new Category(categorization, carGTUType, this.graphLanes.get(0));
392             Category carCategoryR = new Category(categorization, carGTUType, onramp);
393             Category truCategory0 = new Category(categorization, truckGTUType, this.graphLanes.get(0));
394             Category truCategoryR = new Category(categorization, truckGTUType, onramp);
395             Category caccCategory = new Category(categorization, caccGTUType, this.graphLanes.get(0));
396             ODMatrix odMatrix = new ODMatrix("CACC OD", origins, destinations, categorization, timeVector, interpolation);
397 
398             double intensityIncrease = this.intensity;
399             double platoonPenetration = this.penetration;
400 
401             // Demand of trucks departing from main road20
402             List<Double> demandList = new ArrayList<Double>();
403             demandList.add((double) 0);
404             demandList.add((double) 0);
405             demandList.add(180 * (1 + intensityIncrease));
406             demandList.add(180 * (1 + intensityIncrease));
407             demandList.add(180 * (1 + intensityIncrease));
408             demandList.add(180 * (1 + intensityIncrease));
409             demandList.add(180 * (1 + intensityIncrease));
410             demandList.add(180 * (1 + intensityIncrease));
411             demandList.add((double) 0);
412             demandList.add((double) 0);
413             List<Double> demandPlatoon = new ArrayList<Double>(); // List for number of platoons (not number of platoon
414                                                                   // vehicles!)
415             List<Double> demandPlatoonVehicles = new ArrayList<Double>(); // Demand for platoon vehicles
416 
417             for (int k = 0; k < demandList.size(); k++)
418             {
419                 double platoonVehicles = (demandList.get(k) * platoonPenetration);
420                 double platoonPlatoons = (Math.round(platoonVehicles / this.platoonSize)) / 4; 
421                 // Actual generated platoons -> divide by 4 to account for 15 minutes
422                 double newDemandVal = Math.max(demandList.get(k) - platoonVehicles, 0);
423                 demandList.set(k, newDemandVal);
424                 demandPlatoon.add(platoonPlatoons);
425                 platoonVehicles = platoonPlatoons * this.platoonSize * 4; // Based on actual generated platoons, but used to
426                                                                      // compensate per hour
427                 demandPlatoonVehicles.add(platoonVehicles);
428             }
429 
430             // Platoon definition (platoons = ... divide into multiple for multiple origins)
431             Set<LaneDirection> position = new LinkedHashSet<>();
432             position.add(new LaneDirection(this.graphLanes.get(0), GTUDirectionality.DIR_PLUS));
433             DefaultGTUCharacteristicsGeneratorOD characteristicsGenerator =
434                     getCharacteristicsGenerator(longitudinalControllerFactory, sim, parameters);
435             Platoons<Category> platoons =
436                     Platoons.ofCategory(characteristicsGenerator, sim, sim.getReplication().getStream("generation"), position);
437 
438             platoons.fixInfo(nodeA, nodeD, caccCategory);
439 
440             double dt = this.startSpacing;
441 
442             // Determine platoon generation times (of leading vehicle only) using pseudorandom generation (exponential
443             // distribution)
444             // Then check if interval times do not overlap with whole platoon generation, adjust accordingly
445             List<Double> generationTimes = new ArrayList<Double>();
446             double previous = 0;
447 
448             for (int k = 0; k < demandList.size(); k++)
449             {
450                 for (int i = 0; i < demandPlatoon.get(k); i++)
451                 {
452                     double lambda = (demandPlatoon.get(k) * 4); // Number of platoons to be generated (per hour -> times 4)
453                     StreamInterface rand = sim.getReplication().getStream("generation");
454                     // Random rand = new Random();
455                     double arrival = (Math.log(1 - rand.nextDouble()) / (-lambda) * 3600); 
456                     // Inter arrival time is in the unit of lambda (here per 15 minutes)
457 
458                     double startTime = (arrival + previous); // (k * 900) + (arrival + previous); // Interarrival plus 15
459                                                              // minutes (900 seconds)
460                     generationTimes.add(startTime);
461                     previous = previous + arrival;
462                 }
463             }
464 
465             // Sorting the generation time to check for overlap in generation
466             Collections.sort(generationTimes);
467 
468             for (int i = 0; i < generationTimes.size() - 1; i++)
469             {
470                 double diff = generationTimes.get(i + 1) - generationTimes.get(i);
471                 double generationDuration = 0.5;
472                 double maxDuration = (generationDuration * this.platoonSize) + 1.0;
473                 if (diff <= maxDuration)
474                 {
475                     generationTimes.set(i + 1, generationTimes.get(i) + maxDuration);
476                 }
477 
478                 // ((platoonSize-1)*((12.0 + 3.0)/22.22) + 0.3) + ( (12.0 + 3.0)/22.22 + 1.0);
479                 double endTime = generationTimes.get(i) + (generationDuration * this.platoonSize);
480                 platoons.addPlatoon(Time.instantiateSI(generationTimes.get(i)), Time.instantiateSI(endTime));
481 
482                 for (double t = generationTimes.get(i); t < endTime; t += dt)
483                 {
484                     platoons.addGtu(Time.instantiateSI(t));
485                 }
486             }
487 
488             // OD demand
489             // cars (without compensation we use fraction 0.5 in putDemandVector, otherwise we multiply by laneshare)
490             odMatrix.putDemandVector(nodeA, nodeD, carCategory1,
491                     freq(new double[] { 0, 0, 1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease),
492                             1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease), 0,
493                             0 }),
494                     timeVector, interpolation, 0.5);
495             odMatrix.putDemandVector(nodeA, nodeD, carCategory0, platoons.compensate(carCategory0,
496                     freq(new double[] { 0, 0, 1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease),
497                             1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease),
498 
499                             0, 0 }),
500                     timeVector, interpolation), timeVector, interpolation, 0.5);
501             odMatrix.putDemandVector(nodeE, nodeD, carCategoryR,
502                     freq(new double[] { 0, 0, 250 * (1 + intensityIncrease), 250 * (1 + intensityIncrease),
503                             250 * (1 + intensityIncrease), 250 * (1 + intensityIncrease), 250 * (1 + intensityIncrease), 0,
504                             0 }));
505             // trucks
506             odMatrix.putDemandVector(nodeE, nodeD, truCategoryR,
507                     freq(new double[] { 0, 0, 45 * (1 + intensityIncrease), 45 * (1 + intensityIncrease),
508                             45 * (1 + intensityIncrease), 45 * (1 + intensityIncrease), 45 * (1 + intensityIncrease), 0, 0 }));
509             // cacc trucks & compensated normal trucks - if there are no, or only, cacc vehicles -> remove demand
510             if (this.penetration != 1.0)
511             {
512                 odMatrix.putDemandVector(nodeA, nodeD, truCategory0,
513                         platoons.compensate(truCategory0,
514                                 freq(new double[] { demandList.get(0), demandList.get(1), demandList.get(2), demandList.get(3),
515                                         demandList.get(4), demandList.get(5), demandList.get(6), demandList.get(7),
516                                         demandList.get(8) }),
517                                 timeVector, interpolation));
518             }
519             if (this.penetration != 0.0)
520             {
521                 odMatrix.putDemandVector(nodeA, nodeD, caccCategory,
522                         platoons.compensate(caccCategory, freq(new double[] { demandPlatoonVehicles.get(0),
523                                 demandPlatoonVehicles.get(1), demandPlatoonVehicles.get(2), demandPlatoonVehicles.get(3),
524                                 demandPlatoonVehicles.get(4), demandPlatoonVehicles.get(5), demandPlatoonVehicles.get(6),
525                                 demandPlatoonVehicles.get(7), demandPlatoonVehicles.get(8) }), timeVector, interpolation));
526             }
527             // options
528             ODOptions odOptions = new ODOptions().set(ODOptions.NO_LC_DIST, Length.instantiateSI(300.0)).set(ODOptions.GTU_TYPE,
529                     characteristicsGenerator);
530             odApplierOutput = ODApplier.applyOD(network, odMatrix, odOptions);
531 
532             // start platoons
533             platoonDetector1 = "A1";
534             platoons.start(odApplierOutput.get(platoonDetector1).getGenerator());
535 
536             // Write platoon generation times to file
537             BufferedWriter bw;
538             bw = new BufferedWriter(
539                     new OutputStreamWriter(Writer.createOutputStream(this.outputFileGen, CompressionType.NONE)));
540             bw.write(String.format("Platoon generation times [s]: %s", generationTimes));
541             bw.close();
542         }
543         else if (this.networkName.equals("onrampMERGE"))
544         {
545             // points
546             OTSPoint3D pointA = new OTSPoint3D(0, 0);
547             OTSPoint3D pointB = new OTSPoint3D(2000, 0); // 700 meters toerit (1300 - 2000)
548             OTSPoint3D pointC = new OTSPoint3D(2330, 0); // 330 meters onramp
549             OTSPoint3D pointD = new OTSPoint3D(3330, 0);
550             OTSPoint3D pointE = new OTSPoint3D(1300, -40);
551 
552             // nodes
553             OTSRoadNode nodeA = new OTSRoadNode(network, "A", pointA, Direction.ZERO);
554             OTSRoadNode nodeB = new OTSRoadNode(network, "B", pointB, Direction.ZERO);
555             OTSRoadNode nodeC = new OTSRoadNode(network, "C", pointC, Direction.ZERO);
556             OTSRoadNode nodeD = new OTSRoadNode(network, "D", pointD, Direction.ZERO);
557             OTSRoadNode nodeE = new OTSRoadNode(network, "E", pointE, Direction.ZERO);
558 
559             // links
560             CrossSectionLink linkAB = new CrossSectionLink(network, "AB", nodeA, nodeB, freewayLinkType,
561                     new OTSLine3D(pointA, pointB), LaneKeepingPolicy.KEEPRIGHT);
562             CrossSectionLink linkBC = new CrossSectionLink(network, "BC", nodeB, nodeC, freewayLinkType,
563                     new OTSLine3D(pointB, pointC), LaneKeepingPolicy.KEEPRIGHT);
564             CrossSectionLink linkCD = new CrossSectionLink(network, "CD", nodeC, nodeD, freewayLinkType,
565                     new OTSLine3D(pointC, pointD), LaneKeepingPolicy.KEEPRIGHT);
566             CrossSectionLink linkEB = new CrossSectionLink(network, "EB", nodeE, nodeB, freewayLinkType,
567                     Bezier.cubic(nodeE.getLocation(), nodeB.getLocation()), LaneKeepingPolicy.KEEPRIGHT);
568 
569             // lanes and stripes
570             int n = this.numberOfLanes;
571             // List<Lane> originLanes = new ArrayList<>();
572             for (int i = 0; i < n; i++)
573             {
574                 for (CrossSectionLink link : new CrossSectionLink[] { linkAB, linkBC, linkCD })
575                 {
576                     Lane lane = new Lane(link, "Lane " + (i + 1), laneWidth.times((0.5 + i)), laneWidth, freewayLaneType,
577                             new Speed(100, SpeedUnit.KM_PER_HOUR));
578                     Length offset = laneWidth.times(i + 1.0);
579                     Stripe stripe = new Stripe(link, offset, offset, stripeWidth);
580                     if (i < n - 1)
581                     {
582                         if (lane.getParentLink().getId().equals("BC"))
583                         {
584                             // stripe.addPermeability(GTUType.VEHICLE, Permeable.LEFT);
585                             stripe.addPermeability(vehicle, Permeable.BOTH);
586 
587                         }
588                         else
589                         {
590                             stripe.addPermeability(vehicle, Permeable.BOTH);
591                         }
592                     }
593                     if (lane.getParentLink().getId().equals("BC"))
594                     {
595                         new Detector(lane.getFullId(), lane, Length.instantiateSI(330.0), sim);
596                     }
597                     // sink sensors
598                     if (lane.getParentLink().getId().equals("CD"))
599                     {
600                         new SinkSensor(lane, lane.getLength().minus(Length.instantiateSI(100.0)), GTUDirectionality.DIR_PLUS,
601                                 sim);
602                         // detectors 0m after on ramp
603                         // new Detector(lane.getFullId(), lane, Length.createSI(0.0), sim); // id equal to lane, may be
604                         // different
605                     }
606                     if (lane.getParentLink().getId().equals("AB"))
607                     {
608                         // originLanes.add(lane);
609                         this.graphLanes.add(lane);
610                     }
611                 }
612             }
613             new Stripe(linkAB, Length.ZERO, Length.ZERO, stripeWidth);
614             Stripe stripe = new Stripe(linkBC, Length.ZERO, Length.ZERO, stripeWidth);
615             stripe.addPermeability(vehicle, Permeable.LEFT);
616             new Stripe(linkCD, Length.ZERO, Length.ZERO, stripeWidth);
617             new Lane(linkBC, "Acceleration lane", laneWidth.times(-0.5), laneWidth, freewayLaneType,
618                     new Speed(100, SpeedUnit.KM_PER_HOUR));
619             Lane onramp = new Lane(linkEB, "Onramp", laneWidth.times(-0.5), laneWidth, freewayLaneType,
620                     new Speed(100, SpeedUnit.KM_PER_HOUR));
621             new Stripe(linkEB, Length.ZERO, Length.ZERO, stripeWidth);
622             new Stripe(linkEB, laneWidth.neg(), laneWidth.neg(), stripeWidth);
623             new Stripe(linkBC, laneWidth.neg(), laneWidth.neg(), stripeWidth);
624 
625             // OD without demand
626             List<OTSNode> origins = new ArrayList<>();
627             origins.add(nodeA);
628             origins.add(nodeE);
629             List<OTSNode> destinations = new ArrayList<>();
630             destinations.add(nodeD);
631             TimeVector timeVector = DoubleVector.instantiate(new double[] { 0.0, 0.25, 0.50, 0.75, 1.0, 1.25, 1.50, 1.75, 2.0 },
632                     TimeUnit.BASE_HOUR, StorageType.DENSE);
633             Interpolation interpolation = Interpolation.LINEAR; // or STEPWISE
634             Categorization categorization = new Categorization("CACC", GTUType.class, Lane.class);
635             Category carCategory1 = new Category(categorization, carGTUType, this.graphLanes.get(1));
636             Category carCategory0 = new Category(categorization, carGTUType, this.graphLanes.get(0));
637             Category carCategoryR = new Category(categorization, carGTUType, onramp);
638             Category truCategory0 = new Category(categorization, truckGTUType, this.graphLanes.get(0));
639             Category truCategoryR = new Category(categorization, truckGTUType, onramp);
640             Category caccCategory = new Category(categorization, caccGTUType, onramp);
641             ODMatrix odMatrix = new ODMatrix("CACC OD", origins, destinations, categorization, timeVector, interpolation);
642 
643             double intensityIncrease = this.intensity;
644             double platoonPenetration = this.penetration;
645 
646             // Demand of trucks departing from onramp
647             List<Double> demandList = new ArrayList<Double>();
648             demandList.add((double) 0);
649             demandList.add((double) 0);
650             demandList.add(45 * (1 + intensityIncrease));
651             demandList.add(45 * (1 + intensityIncrease));
652             demandList.add(45 * (1 + intensityIncrease));
653             demandList.add(45 * (1 + intensityIncrease));
654             demandList.add(45 * (1 + intensityIncrease));
655 
656             demandList.add((double) 0);
657             demandList.add((double) 0);
658             List<Double> demandPlatoon = new ArrayList<Double>(); // List for number of platoons (not number of platoon
659                                                                   // vehicles!)
660             List<Double> demandPlatoonVehicles = new ArrayList<Double>(); // Demand for platoon vehicles
661 
662             for (int k = 0; k < demandList.size(); k++)
663             {
664                 double platoonVehicles = (demandList.get(k) * platoonPenetration);
665                 // double platoonPlatoons = Math.round(platoonVehicles/this.platoonSize)/4; 
666                 // Actual generated platoons -> divide by 4 to account for 15 minutes
667                 double platoonPlatoons = Math.ceil(Math.abs(platoonVehicles / this.platoonSize)) / 4; // Rounding up
668                 double newDemandVal = Math.max(demandList.get(k) - platoonVehicles, 0);
669                 demandList.set(k, newDemandVal);
670                 demandPlatoon.add(platoonPlatoons);
671                 platoonVehicles = platoonPlatoons * this.platoonSize * 4; // Based on actual generated platoons, but used to
672                                                                      // compensate per hour
673                 demandPlatoonVehicles.add(platoonVehicles);
674             }
675 
676             // Platoon definition (platoons = ... divide into multiple for multiple origins)
677             Set<LaneDirection> position = new LinkedHashSet<>();
678             position.add(new LaneDirection(onramp, GTUDirectionality.DIR_PLUS));
679             DefaultGTUCharacteristicsGeneratorOD characteristicsGenerator =
680                     getCharacteristicsGenerator(longitudinalControllerFactory, sim, parameters);
681             Platoons<Category> platoons =
682                     Platoons.ofCategory(characteristicsGenerator, sim, sim.getReplication().getStream("generation"), position);
683 
684             platoons.fixInfo(nodeE, nodeD, caccCategory);
685 
686             double dt = this.startSpacing;
687 
688             // Determine platoon generation times (of leading vehicle only) using pseudorandom generation (exponential
689             // distribution)
690             // Then check if interval times do not overlap with whole platoon generation, adjust accordingly
691             List<Double> generationTimes = new ArrayList<Double>();
692             double previous = 0;
693 
694             for (int k = 0; k < demandList.size(); k++)
695             {
696                 for (int i = 0; i < demandPlatoon.get(k); i++)
697                 {
698                     double lambda = (demandPlatoon.get(k) * 4); // Number of platoons to be generated (per hour -> times 4)
699                     StreamInterface rand = sim.getReplication().getStream("generation");
700                     // Random rand = new Random();
701                     double arrival = (Math.log(1 - rand.nextDouble()) / (-lambda) * 3600); // Inter arrival time is in the unit
702                                                                                            // of lambda (here per 15 minutes)
703 
704                     double startTime = (arrival + previous); // (k * 900) + (arrival + previous); // Interarrival plus 15
705                                                              // minutes (900 seconds)
706                     generationTimes.add(startTime);
707                     previous = previous + arrival;
708                 }
709             }
710 
711             // Sorting the generation time to check for overlap in generation
712             Collections.sort(generationTimes);
713 
714             for (int i = 0; i < generationTimes.size() - 1; i++)
715             {
716                 double diff = generationTimes.get(i + 1) - generationTimes.get(i);
717                 double generationDuration = 0.5;
718                 double maxDuration = (generationDuration * this.platoonSize) + 1.0;
719                 if (diff <= maxDuration)
720                 {
721                     generationTimes.set(i + 1, generationTimes.get(i) + maxDuration);
722                 }
723 
724                 // ((this.platoonSize-1)*((12.0 + 3.0)/22.22) + 0.3) + ( (12.0 + 3.0)/22.22 + 1.0);
725                 double endTime = generationTimes.get(i) + (generationDuration * this.platoonSize);
726                 platoons.addPlatoon(Time.instantiateSI(generationTimes.get(i)), Time.instantiateSI(endTime));
727 
728                 for (double t = generationTimes.get(i); t < endTime; t += dt)
729                 {
730                     platoons.addGtu(Time.instantiateSI(t));
731                 }
732             }
733             // OD demand
734             // cars (without compensation we use fraction 0.5 in putDemandVector, otherwise we multiply by laneshare)
735             odMatrix.putDemandVector(nodeA, nodeD, carCategory1,
736                     freq(new double[] { 0, 0, 1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease),
737                             1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease),
738 
739                             0, 0 }),
740                     timeVector, interpolation, 0.5);
741             odMatrix.putDemandVector(nodeA, nodeD, carCategory0,
742                     freq(new double[] { 0, 0, 1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease),
743                             1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease), 1000 * (1 + intensityIncrease),
744 
745                             0, 0 }),
746                     timeVector, interpolation, 0.5);
747             odMatrix.putDemandVector(nodeE, nodeD, carCategoryR,
748                     platoons.compensate(carCategoryR,
749                             freq(new double[] { 0, 0, 250 * (1 + intensityIncrease), 250 * (1 + intensityIncrease),
750                                     250 * (1 + intensityIncrease), 250 * (1 + intensityIncrease), 250 * (1 + intensityIncrease),
751 
752                                     0, 0 }),
753                             timeVector, interpolation));
754             // trucks
755             odMatrix.putDemandVector(nodeA, nodeD, truCategory0,
756                     platoons.compensate(truCategory0,
757                             freq(new double[] { 0, 0, 180 * (1 + intensityIncrease), 180 * (1 + intensityIncrease),
758                                     180 * (1 + intensityIncrease), 180 * (1 + intensityIncrease), 180 * (1 + intensityIncrease),
759 
760                                     0, 0 }),
761                             timeVector, interpolation));
762             // cacc trucks & compensated normal trucks - if there are no, or only, cacc vehicles -> remove demand
763             if (this.penetration != 1.0)
764             {
765                 odMatrix.putDemandVector(nodeE, nodeD, truCategoryR,
766                         freq(new double[] { demandList.get(0), demandList.get(1), demandList.get(2), demandList.get(3),
767                                 demandList.get(4), demandList.get(5), demandList.get(6), demandList.get(7),
768                                 demandList.get(8) }),
769                         timeVector, interpolation);
770             }
771             if (this.penetration != 0.0)
772             {
773                 odMatrix.putDemandVector(nodeE, nodeD, caccCategory,
774                         platoons.compensate(caccCategory, freq(new double[] { demandPlatoonVehicles.get(0),
775                                 demandPlatoonVehicles.get(1), demandPlatoonVehicles.get(2), demandPlatoonVehicles.get(3),
776                                 demandPlatoonVehicles.get(4), demandPlatoonVehicles.get(5), demandPlatoonVehicles.get(6),
777                                 demandPlatoonVehicles.get(7), demandPlatoonVehicles.get(8) }), timeVector, interpolation));
778             }
779 
780             // options
781             ODOptions odOptions = new ODOptions().set(ODOptions.NO_LC_DIST, Length.instantiateSI(300.0)).set(ODOptions.GTU_TYPE,
782                     characteristicsGenerator);
783             odApplierOutput = ODApplier.applyOD(network, odMatrix, odOptions);
784 
785             // start platoons
786             platoonDetector1 = "E1";
787             platoons.start(odApplierOutput.get(platoonDetector1).getGenerator());
788 
789             // Write platoon generation times to file
790             BufferedWriter bw;
791             bw = new BufferedWriter(
792                     new OutputStreamWriter(Writer.createOutputStream(this.outputFileGen, CompressionType.NONE)));
793             bw.write(String.format("Platoon generation times [s]: %s", generationTimes));
794             bw.close();
795 
796         }
797         else
798         {
799             throw new RuntimeException("Network " + this.networkName + " not supported.");
800         }
801 
802         // listen to generator so we can add platoon object to the tactical planner (for first direction)
803         odApplierOutput.get(platoonDetector1).getGenerator().addListener(new EventListenerInterface()
804         {
805             /** ... */
806             private static final long serialVersionUID = 1L;
807 
808             /** Current platoon. */
809             private Platoon platoon = null;
810 
811             /** Last generated CACC GTU. */
812             private LaneBasedGTU lastGtu;
813 
814             /** {@inheritDoc} */
815             @Override
816             public void notify(final EventInterface event) throws RemoteException
817             {
818                 if (event.getType().equals(LaneBasedGTUGenerator.GTU_GENERATED_EVENT))
819                 {
820                     LaneBasedGTU gtu = (LaneBasedGTU) event.getContent();
821                     if (gtu.getGTUType().equals(caccGTUType))
822                     {
823                         // If platoon is not within 2 seconds, or platoon is equal or larger than set platoon size -> new
824                         // platoon
825                         // || this.lastGtu.getLocation().distance(gtu.getLocation()) > 2.0 * gtu.getSpeed().si
826 
827                         if (this.lastGtu == null || this.lastGtu.isDestroyed()
828                                 || this.lastGtu.getLocation().distance(gtu.getLocation()) > 3.0 * gtu.getSpeed().si
829                                 || this.platoon.size() >= platoonSize)
830                         {
831                             this.platoon = new Platoon();
832                         }
833                         this.platoon.addGtu(gtu.getId());
834                         ((CaccTacticalPlanner) gtu.getTacticalPlanner()).setPlatoon(this.platoon);
835                         this.lastGtu = gtu;
836                     }
837                 }
838             }
839         }, LaneBasedGTUGenerator.GTU_GENERATED_EVENT);
840 
841         /** Sampler for statistics. */
842         this.sampler =
843                 RoadSampler.build(network).registerExtendedDataType(this.ttc).registerFilterDataType(this.metaGtu).create();
844 
845         // Construct sample time and space window
846         Time start = new Time(0.25, TimeUnit.BASE_HOUR); // TODO set times
847         Time end = new Time(2.00, TimeUnit.BASE_HOUR);
848         for (Link link : network.getLinkMap().values())
849         {
850             for (Lane lane : ((CrossSectionLink) link).getLanes())
851             {
852                 KpiLaneDirection kpiLane = new KpiLaneDirection(new LaneData(lane), KpiGtuDirectionality.DIR_PLUS);
853                 SpaceTimeRegion region = new SpaceTimeRegion(kpiLane, Length.ZERO, lane.getLength(), start, end);
854                 this.regions.add(region);
855                 this.sampler.registerSpaceTimeRegion(region);
856             }
857         }
858         return network;
859     }
860 
861     /** {@inheritDoc} */
862     @Override
863     protected void addTabs(final OTSSimulatorInterface sim, final OTSSimulationApplication<?> animation)
864     {
865         if (this.graphs)
866         {
867             // create tab
868             int h = (int) Math.sqrt(this.graphLanes.size());
869             int w = (int) Math.ceil(((double) this.graphLanes.size()) / h);
870             TablePanel charts = new TablePanel(w, h);
871             animation.getAnimationPanel().getTabbedPane().addTab(animation.getAnimationPanel().getTabbedPane().getTabCount(),
872                     "statistics", charts);
873 
874             // create sampler for the data
875             //RoadSampler graphSampler = new RoadSampler(getNetwork());
876             RoadSampler graphSampler = RoadSampler.build(getNetwork()).registerExtendedDataType(this.ttc)
877                     .registerFilterDataType(this.metaGtu).create();
878 
879             // create plots per lane
880             int h2 = 0;
881             int w2 = 0;
882             for (int i = 0; i < this.graphLanes.size(); i++)
883             {
884                 GraphPath<KpiLaneDirection> graphPath;
885                 try
886                 {
887                     // starts at the lane and includes downstream lanes
888                     graphPath = GraphLaneUtil.createPath("Lane" + (i + 1),
889                             new LaneDirection(this.graphLanes.get(i), GTUDirectionality.DIR_PLUS));
890                     GraphPath.initRecording(graphSampler, graphPath);
891                 }
892                 catch (NetworkException exception)
893                 {
894                     throw new RuntimeException(exception);
895                 }
896                 ContourDataSource<?> dataPool = new ContourDataSource<>(graphSampler.getSamplerData(), graphPath);
897                 SwingContourPlot plot = new SwingContourPlot(new ContourPlotSpeed("Speed lane " + (i + 1), sim, dataPool));
898                 charts.setCell(plot.getContentPane(), w2, h2);
899                 w2++;
900                 if (w2 > w)
901                 {
902                     w = 0;
903                     h2++;
904                 }
905             }
906         }
907     }
908 
909     /** {@inheritDoc} */
910     @Override
911     protected void onSimulationEnd()
912     {
913         // periodic = true; periodic data is stored, if false other mesoscopic data would be stored, but that has not been setup
914         Detector.writeToFile(getNetwork(), this.outputFileDets, true);
915 
916         // this.sampler.writeToFile(getProperty("outputFile"));
917         double tts = 0.0;
918         List<Float> ttcList = new ArrayList<>();
919         List<String> ttcListGtuType = new ArrayList<>();
920         List<Float> decList = new ArrayList<>();
921         List<String> decListGtuType = new ArrayList<>();
922         List<Float> posList = new ArrayList<>();
923         List<Float> timeList = new ArrayList<>();
924         List<String> postimeList = new ArrayList<>();
925         List<String> posListGtuType = new ArrayList<>();
926         List<Float> posupstreamList = new ArrayList<>();
927         List<Float> timeupstreamList = new ArrayList<>();
928         List<String> postimeupstreamList = new ArrayList<>();
929         List<String> posupstreamListGtuType = new ArrayList<>();
930         List<Float> posdownstreamList = new ArrayList<>();
931         List<Float> timedownstreamList = new ArrayList<>();
932         List<String> postimedownstreamList = new ArrayList<>();
933         List<String> posdownstreamListGtuType = new ArrayList<>();
934         List<Float> posrampList = new ArrayList<>();
935         List<Float> timerampList = new ArrayList<>();
936         List<String> postimerampList = new ArrayList<>();
937         List<String> posrampListGtuType = new ArrayList<>();
938         int[] counts = new int[120];
939         Length detectorPosition = Length.instantiateSI(100.0);
940         for (SpaceTimeRegion region : this.regions)
941         {
942             TrajectoryGroup<?> trajectoryGroup =
943                     this.sampler.getSamplerData().getTrajectoryGroup(region.getLaneDirection()).getTrajectoryGroup(
944                             region.getStartPosition(), region.getEndPosition(), region.getStartTime(), region.getEndTime());
945             for (Trajectory<?> trajectory : trajectoryGroup)
946             {
947                 try
948                 {
949                     tts += trajectory.getTotalDuration().si;
950                     String gtuTypeId = trajectory.getMetaData(this.metaGtu /*new FilterDataGtuType()*/).getId();
951                     for (FloatDuration ttcVal : trajectory.getExtendedData(this.ttc))
952                     {
953                         if (!Float.isNaN(ttcVal.si) && ttcVal.si < 20)
954                         {
955                             ttcList.add(ttcVal.si);
956                             ttcListGtuType.add(gtuTypeId);
957                         }
958                     }
959                     for (float decVal : trajectory.getA())
960                     {
961                         if (decVal < -2.0)
962                         {
963                             decList.add(decVal);
964                             decListGtuType.add(gtuTypeId);
965                         }
966                     }
967 
968                     // collect data for merging lane and acceleration lane
969                     if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("BC")
970                             && region.getLaneDirection().getLaneData().getId().equals("Lane 1"))
971                     {
972                         for (float xPos : trajectory.getX())
973                         {
974                             posList.add(xPos);
975                             posListGtuType.add(trajectory.getGtuId());
976                             postimeList.add(gtuTypeId);
977                         }
978 
979                         for (float tPos : trajectory.getT())
980                         {
981                             timeList.add(tPos);
982                         }
983                     }
984 
985                     if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("AB")
986                             && region.getLaneDirection().getLaneData().getId().equals("Lane 1"))
987                     {
988                         for (float xPos : trajectory.getX())
989                         {
990                             posupstreamList.add(xPos);
991                             posupstreamListGtuType.add(trajectory.getGtuId());
992                             postimeupstreamList.add(gtuTypeId);
993                         }
994 
995                         for (float tPos : trajectory.getT())
996                         {
997                             timeupstreamList.add(tPos);
998                         }
999                     }
1000 
1001                     if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("CD")
1002                             && region.getLaneDirection().getLaneData().getId().equals("Lane 1"))
1003                     {
1004                         for (float xPos : trajectory.getX())
1005                         {
1006                             posdownstreamList.add(xPos);
1007                             posdownstreamListGtuType.add(trajectory.getGtuId());
1008                             postimedownstreamList.add(gtuTypeId);
1009                         }
1010 
1011                         for (float tPos : trajectory.getT())
1012                         {
1013                             timedownstreamList.add(tPos);
1014                         }
1015                     }
1016 
1017                     if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("BC")
1018                             && region.getLaneDirection().getLaneData().getId().equals("Acceleration lane"))
1019                     {
1020                         for (float xPos : trajectory.getX())
1021                         {
1022                             posrampList.add(xPos);
1023                             posrampListGtuType.add(trajectory.getGtuId());
1024                             postimerampList.add(gtuTypeId);
1025                         }
1026 
1027                         for (float tPos : trajectory.getT())
1028                         {
1029                             timerampList.add(tPos);
1030                         }
1031                     }
1032                     if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("CD") && trajectory.size() > 1
1033                             && trajectory.getX(0) < detectorPosition.si
1034                             && trajectory.getX(trajectory.size() - 1) > detectorPosition.si)
1035                     {
1036                         double t = trajectory.getTimeAtPosition(detectorPosition).si - region.getStartTime().si;
1037                         counts[(int) (t / 60.0)]++;
1038                     }
1039                 }
1040                 catch (SamplingException exception)
1041                 {
1042                     throw new RuntimeException("Unexpected exception: TimeToCollission not available or index out of bounds.",
1043                             exception);
1044                 }
1045             }
1046         }
1047         int qMax = 0;
1048         for (int i = 0; i < counts.length - 4; i++)
1049         {
1050             int q = 0;
1051             for (int j = i; j < i + 5; j++)
1052             {
1053                 q += counts[j];
1054             }
1055             qMax = qMax > q ? qMax : q;
1056         }
1057         BufferedWriter bw;
1058         try
1059         {
1060             bw = new BufferedWriter(
1061                     new OutputStreamWriter(Writer.createOutputStream(this.outputFileTraj, CompressionType.NONE)));
1062             bw.write(String.format("total time spent [s]: %.0f", tts));
1063             bw.newLine();
1064             bw.write(String.format("maximum flow [veh/5min]: %d", qMax));
1065             bw.newLine();
1066             bw.write(String.format("time to collision [s]: %s", ttcList));
1067             bw.newLine();
1068             bw.write(String.format("time to collision GTU type: %s", ttcListGtuType));
1069             bw.newLine();
1070             bw.write(String.format("strong decelerations [m/s^2]: %s", decList));
1071             bw.newLine();
1072             bw.write(String.format("strong decelerations GTU type: %s", decListGtuType));
1073             bw.newLine();
1074             bw.write(String.format("X pos: %s", posList));
1075             bw.newLine();
1076             bw.write(String.format("T pos: %s", timeList));
1077             bw.newLine();
1078             bw.write(String.format("X pos GTU ID: %s", posListGtuType));
1079             bw.newLine();
1080             bw.write(String.format("X pos GTU type: %s", postimeList));
1081             bw.newLine();
1082             bw.write(String.format("X pos ramp: %s", posrampList));
1083             bw.newLine();
1084             bw.write(String.format("T pos ramp: %s", timerampList));
1085             bw.newLine();
1086             bw.write(String.format("X pos ramp GTU ID: %s", posrampListGtuType));
1087             bw.newLine();
1088             bw.write(String.format("X pos ramp GTU type: %s", postimerampList));
1089             bw.newLine();
1090             bw.write(String.format("X pos up: %s", posupstreamList));
1091             bw.newLine();
1092             bw.write(String.format("T pos up: %s", timeupstreamList));
1093             bw.newLine();
1094             bw.write(String.format("X pos up GTU ID: %s", posupstreamListGtuType));
1095             bw.newLine();
1096             bw.write(String.format("X pos up GTU type: %s", postimeupstreamList));
1097             bw.newLine();
1098             bw.write(String.format("X pos down: %s", posdownstreamList));
1099             bw.newLine();
1100             bw.write(String.format("T pos down: %s", timedownstreamList));
1101             bw.newLine();
1102             bw.write(String.format("X pos down GTU ID: %s", posdownstreamListGtuType));
1103             bw.newLine();
1104             bw.write(String.format("X pos down GTU type: %s", postimedownstreamList));
1105             bw.close();
1106         }
1107         catch (IOException exception)
1108         {
1109             throw new RuntimeException(exception);
1110         }
1111     }
1112 
1113     /**
1114      * Creates a frequency vector.
1115      * @param array double[]; array in veh/h
1116      * @return FrequencyVector; frequency vector
1117      * @throws ValueRuntimeException on problem
1118      */
1119     private FrequencyVector freq(final double[] array) throws ValueRuntimeException
1120     {
1121         return DoubleVector.instantiate(array, FrequencyUnit.PER_HOUR, StorageType.DENSE);
1122     }
1123 
1124     /**
1125      * Returns an OD compatible characteristics generator that uses the GTUType to select either regular models or CACC specific
1126      * models.
1127      * @param longitudinalControllerFactory LongitudinalControllerFactory&lt;? extends CACC&gt;; longitudinal controller factory
1128      * @param simulator OTSSimulatorInterface; simulator
1129      * @param parameters ParameterFactory parameters
1130      * @return OD compatible characteristics generator
1131      */
1132     private DefaultGTUCharacteristicsGeneratorOD getCharacteristicsGenerator(
1133             final LongitudinalControllerFactory<? extends CaccController> longitudinalControllerFactory,
1134             final OTSSimulatorInterface simulator, final ParameterFactory parameters)
1135     {
1136         // create template for CACC truck properties (this is then used instead of GTUType.defaultCharacteristics(...))
1137         Set<TemplateGTUType> templates = new LinkedHashSet<>();
1138         templates.add(new TemplateGTUType(caccGTUType, new ConstantGenerator<>(Length.instantiateSI(12.0)),
1139                 new ConstantGenerator<>(Length.instantiateSI(2.55)),
1140                 new ConstantGenerator<>(Speed.instantiateSI(this.setspeed.si + 2.78))));
1141         // anonymous class for factory supplier that overwrites the getFactory() method to return one based on the GTU type
1142         return new DefaultGTUCharacteristicsGeneratorOD(templates, new StrategicalPlannerFactorySupplierOD()
1143         {
1144             /** Strategical planner for regular traffic. */
1145             private LaneBasedStrategicalPlannerFactory<?> lmrsFactory = null;
1146 
1147             /** Strategical planner for CACC trucks. */
1148             private LaneBasedStrategicalPlannerFactory<?> caccFactory = null;
1149 
1150             /** {@inheritDoc} */
1151             @Override
1152             public LaneBasedStrategicalPlannerFactory<?> getFactory(final Node origin, final Node destination,
1153                     final Category category, final StreamInterface randomStream) throws GTUException
1154             {
1155                 GTUType gtuType = category.get(GTUType.class);
1156                 if (!gtuType.equals(caccGTUType))
1157                 {
1158                     if (this.lmrsFactory == null)
1159                     {
1160                         this.lmrsFactory = new LaneBasedStrategicalRoutePlannerFactory(
1161                                 new LMRSFactory(new IDMPlusFactory(randomStream), new DefaultLMRSPerceptionFactory()),
1162                                 parameters);
1163                     }
1164                     return this.lmrsFactory;
1165                 }
1166                 if (this.caccFactory == null)
1167                 {
1168                     this.caccFactory = new LaneBasedStrategicalRoutePlannerFactory(
1169                             new CaccTacticalPlannerFactory(new IDMPlusFactory(randomStream), longitudinalControllerFactory,
1170                                     simulator, caccGTUType),
1171                             parameters);
1172                 }
1173                 return this.caccFactory;
1174             }
1175         });
1176     }
1177     
1178 }