View Javadoc
1   package org.opentrafficsim.demo.sdm;
2   
3   import java.awt.Color;
4   import java.io.BufferedWriter;
5   import java.io.IOException;
6   import java.io.OutputStreamWriter;
7   import java.util.ArrayList;
8   import java.util.List;
9   
10  import org.djunits.unit.FrequencyUnit;
11  import org.djunits.unit.SpeedUnit;
12  import org.djunits.unit.TimeUnit;
13  import org.djunits.value.StorageType;
14  import org.djunits.value.ValueException;
15  import org.djunits.value.vdouble.scalar.Acceleration;
16  import org.djunits.value.vdouble.scalar.Duration;
17  import org.djunits.value.vdouble.scalar.Length;
18  import org.djunits.value.vdouble.scalar.Speed;
19  import org.djunits.value.vdouble.scalar.Time;
20  import org.djunits.value.vdouble.vector.FrequencyVector;
21  import org.djunits.value.vdouble.vector.TimeVector;
22  import org.djunits.value.vfloat.scalar.FloatDuration;
23  import org.opentrafficsim.base.compressedfiles.CompressionType;
24  import org.opentrafficsim.base.compressedfiles.Writer;
25  import org.opentrafficsim.core.animation.gtu.colorer.AccelerationGTUColorer;
26  import org.opentrafficsim.core.animation.gtu.colorer.SpeedGTUColorer;
27  import org.opentrafficsim.core.animation.gtu.colorer.SwitchableGTUColorer;
28  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
29  import org.opentrafficsim.core.geometry.OTSPoint3D;
30  import org.opentrafficsim.core.gtu.GTUDirectionality;
31  import org.opentrafficsim.core.gtu.GTUType;
32  import org.opentrafficsim.core.network.LinkType;
33  import org.opentrafficsim.core.network.NetworkException;
34  import org.opentrafficsim.core.network.OTSNode;
35  import org.opentrafficsim.core.perception.HistoryManagerDEVS;
36  import org.opentrafficsim.demo.steering.SteeringSimulation;
37  import org.opentrafficsim.draw.graphs.ContourDataSource;
38  import org.opentrafficsim.draw.graphs.ContourPlotSpeed;
39  import org.opentrafficsim.draw.graphs.GraphPath;
40  import org.opentrafficsim.draw.graphs.road.GraphLaneUtil;
41  import org.opentrafficsim.kpi.sampling.KpiGtuDirectionality;
42  import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
43  import org.opentrafficsim.kpi.sampling.SamplingException;
44  import org.opentrafficsim.kpi.sampling.SpaceTimeRegion;
45  import org.opentrafficsim.kpi.sampling.Trajectory;
46  import org.opentrafficsim.kpi.sampling.TrajectoryGroup;
47  import org.opentrafficsim.road.gtu.colorer.DesiredHeadwayColorer;
48  import org.opentrafficsim.road.gtu.colorer.DistractionColorer;
49  import org.opentrafficsim.road.gtu.colorer.FixedColor;
50  import org.opentrafficsim.road.gtu.colorer.SynchronizationColorer;
51  import org.opentrafficsim.road.gtu.colorer.TaskSaturationColorer;
52  import org.opentrafficsim.road.gtu.generator.od.DefaultGTUCharacteristicsGeneratorOD;
53  import org.opentrafficsim.road.gtu.generator.od.ODApplier;
54  import org.opentrafficsim.road.gtu.generator.od.ODOptions;
55  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
56  import org.opentrafficsim.road.gtu.lane.perception.mental.AdaptationSituationalAwareness;
57  import org.opentrafficsim.road.gtu.lane.perception.mental.ExponentialTask;
58  import org.opentrafficsim.road.gtu.lane.perception.mental.Task;
59  import org.opentrafficsim.road.gtu.lane.perception.mental.sdm.DefaultDistraction;
60  import org.opentrafficsim.road.gtu.lane.perception.mental.sdm.DistractionFactory;
61  import org.opentrafficsim.road.gtu.lane.perception.mental.sdm.StochasticDistractionModel;
62  import org.opentrafficsim.road.gtu.lane.perception.mental.sdm.TaskSupplier;
63  import org.opentrafficsim.road.gtu.strategical.od.Categorization;
64  import org.opentrafficsim.road.gtu.strategical.od.Category;
65  import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
66  import org.opentrafficsim.road.gtu.strategical.od.ODMatrix;
67  import org.opentrafficsim.road.network.OTSRoadNetwork;
68  import org.opentrafficsim.road.network.factory.LaneFactory;
69  import org.opentrafficsim.road.network.lane.CrossSectionLink;
70  import org.opentrafficsim.road.network.lane.Lane;
71  import org.opentrafficsim.road.network.lane.LaneDirection;
72  import org.opentrafficsim.road.network.lane.LaneType;
73  import org.opentrafficsim.road.network.lane.Stripe.Permeable;
74  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
75  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
76  import org.opentrafficsim.road.network.sampling.LaneData;
77  import org.opentrafficsim.road.network.sampling.RoadSampler;
78  import org.opentrafficsim.road.network.sampling.data.TimeToCollision;
79  import org.opentrafficsim.swing.gui.OTSSimulationApplication;
80  import org.opentrafficsim.swing.script.AbstractSimulationScript;
81  
82  import nl.tudelft.simulation.dsol.swing.gui.TablePanel;
83  import nl.tudelft.simulation.jstats.streams.StreamInterface;
84  
85  /**
86   * Script to run a simulation with the Stochastic Distraction Model.
87   * <p>
88   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
89   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
90   * <p>
91   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 5 nov. 2018 <br>
92   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
93   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
94   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
95   */
96  public class SdmSimulation extends AbstractSimulationScript
97  {
98      /** Network. */
99      private OTSRoadNetwork network;
100 
101     /** Sampler for statistics. */
102     private RoadSampler sampler;
103 
104     /** Time to collision data type. */
105     private final TimeToCollision ttc = new TimeToCollision();
106 
107     /** Space time regions to sample traffic on. */
108     private final List<SpaceTimeRegion> regions = new ArrayList<>();
109 
110     /**
111      * Constructor.
112      * @param properties String[]; command line arguments
113      */
114     protected SdmSimulation(final String[] properties)
115     {
116         super("SDM simulation", "Simulations using the Stochastic Distraction Model", properties);
117         // set GTU colorers to use
118         setGtuColorer(SwitchableGTUColorer.builder().addActiveColorer(new FixedColor(Color.BLUE, "Blue"))
119                 .addColorer(new SynchronizationColorer())
120                 .addColorer(new DistractionColorer(DefaultDistraction.ANSWERING_CELL_PHONE, DefaultDistraction.CONVERSING,
121                         DefaultDistraction.MANIPULATING_AUDIO_CONTROLS, DefaultDistraction.EXTERNAL_DISTRACTION))
122                 .addColorer(new SpeedGTUColorer(new Speed(150, SpeedUnit.KM_PER_HOUR)))
123                 .addColorer(new AccelerationGTUColorer(Acceleration.createSI(-6.0), Acceleration.createSI(2)))
124                 .addColorer(new DesiredHeadwayColorer(Duration.createSI(0.56), Duration.createSI(2.4)))
125                 .addColorer(new TaskSaturationColorer()).build());
126     }
127 
128     /**
129      * Start a simulation.
130      * @param args String...; command line arguments
131      */
132     public static void main(final String... args)
133     {
134         try
135         {
136             new SdmSimulation(args).start();
137         }
138         catch (Exception ex)
139         {
140             ex.printStackTrace();
141         }
142     }
143 
144     /** {@inheritDoc} */
145     @Override
146     protected void setDefaultProperties()
147     {
148         // output
149         setProperty("outputFile", "output.txt");
150         setProperty("output", true);
151         setProperty("plots", false);
152 
153         // traffic
154         setProperty("startTime", "0");
155         setProperty("warmupTime", "300");
156         setProperty("simulationTime", "3900");
157         setProperty("truckFraction", 0.05);
158         setProperty("leftDemand", 3600);
159         setProperty("rightDemand", 3600);
160         setProperty("startDemandFactor", 0.45);
161 
162         // distractions
163         setProperty("allowMultiTasking", "false");
164         // 1=TALKING_CELL_PHONE,12=CONVERSING,5=MANIPULATING_AUDIO_CONTROLS,16=EXTERNAL_DISTRACTION
165         setProperty("distractions", "1,12,5,16");
166         setProperty("phoneInit", 1.0);
167         setProperty("phoneFinal", 0.3);
168         setProperty("phoneTau", 10.0);
169         setProperty("conversing", 0.3);
170         setProperty("audio", 0.3);
171         setProperty("externalBase", 0.2);
172         setProperty("externalVar", 0.3);
173 
174         // basic behavioral parameters
175         setProperty("DT", 0.5);
176         setProperty("TMIN", 0.56);
177         setProperty("TMAX", 1.2);
178         setProperty("A_CAR", 1.25);
179         setProperty("A_TRUCK", 0.8);
180         setProperty("B", 2.09);
181 
182         // human factors
183         setProperty("SA_MIN", 0.5);
184         setProperty("SA_MAX", 1.0);
185         setProperty("TR_MAX", 2.0);
186         setProperty("TC", 1.0);
187         setProperty("TS_CRIT", 0.8);
188         setProperty("TS_MAX", 2.0);
189         setProperty("BETA_T", 1.0);
190 
191     }
192 
193     /** {@inheritDoc} */
194     @Override
195     protected OTSRoadNetwork setupSimulation(final OTSSimulatorInterface sim) throws Exception
196     {
197         // manager of historic information to allow a reaction time
198         sim.getReplication().setHistoryManager(
199                 new HistoryManagerDEVS(sim, AdaptationSituationalAwareness.TR_MAX.getDefaultValue(), Duration.createSI(10.0)));
200 
201         // Network
202         this.network = new OTSRoadNetwork("SDM", true);
203         OTSPoint3D pointA = new OTSPoint3D(0.0, 0.0);
204         OTSPoint3D pointB = new OTSPoint3D(0.0, -20.0);
205         OTSPoint3D pointC = new OTSPoint3D(1600.0, -20.0);
206         OTSPoint3D pointD = new OTSPoint3D(2000.0, 0.0);
207         OTSPoint3D pointE = new OTSPoint3D(2500.0, 0.0);
208         OTSPoint3D pointF = new OTSPoint3D(3500.0, 0.0);
209         OTSNode nodeA = new OTSNode(this.network, "A", pointA);
210         OTSNode nodeB = new OTSNode(this.network, "B", pointB);
211         OTSNode nodeC = new OTSNode(this.network, "C", pointC);
212         OTSNode nodeD = new OTSNode(this.network, "D", pointD);
213         OTSNode nodeE = new OTSNode(this.network, "E", pointE);
214         OTSNode nodeF = new OTSNode(this.network, "F", pointF);
215         LinkType type = this.network.getLinkType(LinkType.DEFAULTS.FREEWAY);
216         LaneKeepingPolicy policy = LaneKeepingPolicy.KEEPRIGHT;
217         Length laneWidth = Length.createSI(3.5);
218         LaneType laneType = this.network.getLaneType(LaneType.DEFAULTS.FREEWAY);
219         Speed speedLimit = new Speed(120.0, SpeedUnit.KM_PER_HOUR);
220         List<Lane> allLanes = new ArrayList<>();
221         allLanes.addAll(new LaneFactory(this.network, nodeA, nodeD, type, sim, policy)
222                 .leftToRight(2.0, laneWidth, laneType, speedLimit).addLanes(Permeable.BOTH).getLanes());
223         allLanes.addAll(new LaneFactory(this.network, nodeB, nodeC, type, sim, policy)
224                 .leftToRight(0.0, laneWidth, laneType, speedLimit).addLanes(Permeable.BOTH).getLanes());
225         allLanes.addAll(new LaneFactory(this.network, nodeC, nodeD, type, sim, policy)
226                 .leftToRight(0.0, laneWidth, laneType, speedLimit).addLanes(Permeable.BOTH).getLanes());
227         allLanes.addAll(
228                 new LaneFactory(this.network, nodeD, nodeE, type, sim, policy).leftToRight(2.0, laneWidth, laneType, speedLimit)
229                         .addLanes(Permeable.BOTH, Permeable.BOTH, Permeable.BOTH).getLanes());
230         List<Lane> lanesEF = new LaneFactory(this.network, nodeE, nodeF, type, sim, policy)
231                 .leftToRight(1.0, laneWidth, laneType, speedLimit).addLanes(Permeable.BOTH, Permeable.BOTH).getLanes();
232         allLanes.addAll(lanesEF);
233         for (Lane lane : lanesEF)
234         {
235             new SinkSensor(lane, lane.getLength().minus(Length.createSI(50)), sim);
236         }
237 
238         // OD
239         List<OTSNode> origins = new ArrayList<>();
240         origins.add(nodeA);
241         origins.add(nodeB);
242         List<OTSNode> destinations = new ArrayList<>();
243         destinations.add(nodeF);
244         double wut = sim.getReplication().getTreatment().getWarmupPeriod().si;
245         double rl = sim.getReplication().getTreatment().getRunLength().si;
246         TimeVector timeVector =
247                 new TimeVector(new double[] {0.0, wut, wut + (rl - wut) * 0.5, rl}, TimeUnit.BASE, StorageType.DENSE);
248         Interpolation interpolation = Interpolation.LINEAR;
249         Categorization categorization = new Categorization("GTU categorization", GTUType.class);
250         ODMatrix odMatrix = new ODMatrix("OD", origins, destinations, categorization, timeVector, interpolation);
251         Category carCategory = new Category(categorization, this.network.getGtuType(GTUType.DEFAULTS.CAR));
252         Category truCategory = new Category(categorization, this.network.getGtuType(GTUType.DEFAULTS.TRUCK));
253         double f1 = getDoubleProperty("truckFraction");
254         double f2 = 1.0 - f1;
255         double left2 = getDoubleProperty("leftDemand");
256         double right2 = getDoubleProperty("rightDemand");
257         double startDemandFactor = getDoubleProperty("startDemandFactor");
258         double left1 = left2 * startDemandFactor;
259         double right1 = right2 * startDemandFactor;
260         odMatrix.putDemandVector(nodeA, nodeF, carCategory, freq(new double[] {f2 * left1, f2 * left1, f2 * left2, 0.0}));
261         odMatrix.putDemandVector(nodeA, nodeF, truCategory, freq(new double[] {f1 * left1, f1 * left1, f1 * left2, 0.0}));
262         odMatrix.putDemandVector(nodeB, nodeF, carCategory, freq(new double[] {f2 * right1, f2 * right1, f2 * right2, 0.0}));
263         odMatrix.putDemandVector(nodeB, nodeF, truCategory, freq(new double[] {f1 * right1, f1 * right1, f1 * right2, 0.0}));
264         ODOptions odOptions = new ODOptions().set(ODOptions.NO_LC_DIST, Length.createSI(200)).set(ODOptions.GTU_TYPE,
265                 new DefaultGTUCharacteristicsGeneratorOD(
266                         new SdmStrategicalPlannerFactory(this.network, sim.getReplication().getStream("generation"), this)));
267         ODApplier.applyOD(this.network, odMatrix, sim, odOptions);
268 
269         // setup the SDM
270         DistractionFactory distFactory = new DistractionFactory(sim.getReplication().getStream("default"));
271         for (String distraction : getProperty("distractions").split(","))
272         {
273             DefaultDistraction dist = DefaultDistraction.values()[Integer.parseInt(distraction) - 1];
274             distFactory.addDistraction(dist, getTaskSupplier(dist, sim.getReplication().getStream("default")));
275         }
276         new StochasticDistractionModel(getBooleanProperty("allowMultiTasking"), distFactory.build(), sim, this.network);
277 
278         // sampler
279         if (getBooleanProperty("output"))
280         {
281             this.sampler = new RoadSampler(sim);
282             Time start = new Time(0.05, TimeUnit.BASE_HOUR);
283             Time end = new Time(1.05, TimeUnit.BASE_HOUR);
284             for (Lane lane : allLanes)
285             {
286                 KpiLaneDirection kpiLane = new KpiLaneDirection(new LaneData(lane), KpiGtuDirectionality.DIR_PLUS);
287                 SpaceTimeRegion region = new SpaceTimeRegion(kpiLane, Length.ZERO, lane.getLength(), start, end);
288                 this.regions.add(region);
289                 this.sampler.registerSpaceTimeRegion(region);
290             }
291             this.sampler.registerExtendedDataType(this.ttc);
292         }
293 
294         // return network
295         return this.network;
296     }
297 
298     /** {@inheritDoc} */
299     @Override
300     protected void addTabs(final OTSSimulatorInterface sim, final OTSSimulationApplication<?> animation)
301     {
302         if (!getBooleanProperty("output") || !getBooleanProperty("plots"))
303         {
304             return;
305         }
306         try
307         {
308             TablePanel charts = new TablePanel(2, 2);
309             GraphPath<KpiLaneDirection> path1 = GraphLaneUtil.createPath("Left road, left lane",
310                     new LaneDirection((Lane) ((CrossSectionLink) this.network.getLink("AD")).getCrossSectionElement("Lane 1"),
311                             GTUDirectionality.DIR_PLUS));
312             GraphPath<KpiLaneDirection> path2 = GraphLaneUtil.createPath("Left road, right lane",
313                     new LaneDirection((Lane) ((CrossSectionLink) this.network.getLink("AD")).getCrossSectionElement("Lane 2"),
314                             GTUDirectionality.DIR_PLUS));
315             GraphPath<KpiLaneDirection> path3 = GraphLaneUtil.createPath("Right road, left lane",
316                     new LaneDirection((Lane) ((CrossSectionLink) this.network.getLink("BC")).getCrossSectionElement("Lane 1"),
317                             GTUDirectionality.DIR_PLUS));
318             GraphPath<KpiLaneDirection> path4 = GraphLaneUtil.createPath("Right road, right lane",
319                     new LaneDirection((Lane) ((CrossSectionLink) this.network.getLink("BC")).getCrossSectionElement("Lane 2"),
320                             GTUDirectionality.DIR_PLUS));
321             charts.setCell(new ContourPlotSpeed("Left road, left lane", sim, new ContourDataSource<>(this.sampler, path1))
322                     .getContentPane(), 0, 0);
323             charts.setCell(new ContourPlotSpeed("Left road, right lane", sim, new ContourDataSource<>(this.sampler, path2))
324                     .getContentPane(), 1, 0);
325             charts.setCell(new ContourPlotSpeed("Right road, left lane", sim, new ContourDataSource<>(this.sampler, path3))
326                     .getContentPane(), 0, 1);
327             charts.setCell(new ContourPlotSpeed("Right road, right lane", sim, new ContourDataSource<>(this.sampler, path4))
328                     .getContentPane(), 1, 1);
329             animation.getAnimationPanel().getTabbedPane().addTab(animation.getAnimationPanel().getTabbedPane().getTabCount(),
330                     "statistics ", charts);
331         }
332         catch (NetworkException exception)
333         {
334             throw new RuntimeException(exception);
335         }
336     }
337 
338     /**
339      * Creates a frequency vector.
340      * @param array double[]; array in veh/h
341      * @return FrequencyVector; frequency vector
342      * @throws ValueException on problem
343      */
344     private FrequencyVector freq(final double[] array) throws ValueException
345     {
346         return new FrequencyVector(array, FrequencyUnit.PER_HOUR, StorageType.DENSE);
347     }
348 
349     /**
350      * Returns a task supplier for a distraction. These are specific to the SDM simulations.
351      * @param distraction DefaultDistraction; distraction
352      * @param stream StreamInterface; random number stream for randomized aspects of the distraction
353      * @return TaskSupplier; task supplier
354      */
355     private TaskSupplier getTaskSupplier(final DefaultDistraction distraction, final StreamInterface stream)
356     {
357         switch (distraction)
358         {
359             case TALKING_CELL_PHONE:
360             {
361                 return new TaskSupplier()
362                 {
363                     /** {@inheritDoc} */
364                     @Override
365                     public Task getTask(final LaneBasedGTU gtu)
366                     {
367                         return new ExponentialTask(distraction.getId(), getDoubleProperty("phoneInit"),
368                                 getDoubleProperty("phoneFinal"), getDoubleProperty("phoneTau"), gtu.getSimulator());
369                     }
370                 };
371             }
372             case CONVERSING:
373             {
374                 return new TaskSupplier.Constant(distraction.getId(), getDoubleProperty("conversing"));
375             }
376             case MANIPULATING_AUDIO_CONTROLS:
377             {
378                 return new TaskSupplier.Constant(distraction.getId(), getDoubleProperty("audio"));
379             }
380             case EXTERNAL_DISTRACTION:
381             {
382                 return new TaskSupplier.Constant(distraction.getId(),
383                         getDoubleProperty("externalBase") + getDoubleProperty("externalVar") * stream.nextDouble());
384             }
385             default:
386                 throw new IllegalArgumentException("Distraction " + distraction + " is not recognized.");
387         }
388     }
389 
390     /** {@inheritDoc} */
391     @Override
392     protected void onSimulationEnd()
393     {
394         if (getBooleanProperty("output"))
395         {
396             Length preDetectorPosition = Length.createSI(400.0); // on link DE, upstream of lane drop
397             Length postDetectorPosition = Length.createSI(100.0); // on link EF, downstream of lane drop
398             double tts = 0.0;
399             List<Float> ttcList = new ArrayList<>();
400             List<Float> decList = new ArrayList<>();
401             int[] counts = new int[60];
402             int[] speedCounts = new int[60];
403             double[] speedSum = new double[60];
404             for (SpaceTimeRegion region : this.regions)
405             {
406                 TrajectoryGroup<?> trajectoryGroup =
407                         this.sampler.getTrajectoryGroup(region.getLaneDirection()).getTrajectoryGroup(region.getStartPosition(),
408                                 region.getEndPosition(), region.getStartTime(), region.getEndTime());
409                 for (Trajectory<?> trajectory : trajectoryGroup)
410                 {
411                     try
412                     {
413                         tts += trajectory.getTotalDuration().si;
414                         for (FloatDuration ttcVal : trajectory.getExtendedData(this.ttc))
415                         {
416                             if (!Float.isNaN(ttcVal.si) && ttcVal.si < 20)
417                             {
418                                 ttcList.add(ttcVal.si);
419                             }
420                         }
421                         for (float decVal : trajectory.getA())
422                         {
423                             if (decVal < -2.0)
424                             {
425                                 decList.add(decVal);
426                             }
427                         }
428                         if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("DE") && trajectory.size() > 1
429                                 && trajectory.getX(0) < preDetectorPosition.si
430                                 && trajectory.getX(trajectory.size() - 1) > preDetectorPosition.si)
431                         {
432                             double t = trajectory.getTimeAtPosition(postDetectorPosition).si - region.getStartTime().si;
433                             double v = trajectory.getSpeedAtPosition(postDetectorPosition).si;
434                             speedCounts[(int) (t / 60.0)]++;
435                             speedSum[(int) (t / 60.0)] += v;
436                         }
437                         if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("EF") && trajectory.size() > 1
438                                 && trajectory.getX(0) < postDetectorPosition.si
439                                 && trajectory.getX(trajectory.size() - 1) > postDetectorPosition.si)
440                         {
441                             double t = trajectory.getTimeAtPosition(postDetectorPosition).si - region.getStartTime().si;
442                             counts[(int) (t / 60.0)]++;
443                         }
444                     }
445                     catch (SamplingException exception)
446                     {
447                         throw new RuntimeException(
448                                 "Unexpected exception: TimeToCollission not available or index out of bounds.", exception);
449                     }
450                 }
451             }
452             double qMax = 0;
453             for (int i = 0; i < counts.length - 4; i++)
454             {
455                 int q = 0;
456                 for (int j = i; j < i + 5; j++)
457                 {
458                     q += counts[j];
459                 }
460                 qMax = qMax > q ? qMax : q;
461             }
462             qMax *= 12; // twelve periods of 5min in an hour
463             int n = 0;
464             int countSum = 0;
465             for (int i = 0; i < counts.length; i++)
466             {
467                 double v = speedSum[i] / speedCounts[i];
468                 if (v < 80 / 3.6)
469                 {
470                     countSum += counts[i];
471                     n++;
472                 }
473             }
474             double qSat = n == 0 ? Double.NaN : 60.0 * countSum / n; // per min -> per hour
475             BufferedWriter bw;
476             try
477             {
478                 bw = new BufferedWriter(
479                         new OutputStreamWriter(Writer.createOutputStream(getProperty("outputFile"), CompressionType.ZIP)));
480                 bw.write(String.format("total time spent [s]: %.0f", tts));
481                 bw.newLine();
482                 bw.write(String.format("maximum flow [veh/h]: %.3f", qMax));
483                 bw.newLine();
484                 bw.write(String.format("saturation flow [veh/h]: %.3f", qSat));
485                 bw.newLine();
486                 bw.write(String.format("time to collision [s]: %s", ttcList));
487                 bw.newLine();
488                 bw.write(String.format("strong decelerations [m/s^2]: %s", decList));
489                 bw.close();
490             }
491             catch (IOException exception)
492             {
493                 throw new RuntimeException(exception);
494             }
495         }
496     }
497 
498 }