View Javadoc
1   package org.opentrafficsim.road.network.lane.object.sensor;
2   
3   import java.io.BufferedWriter;
4   import java.io.IOException;
5   import java.util.ArrayList;
6   import java.util.Comparator;
7   import java.util.LinkedHashMap;
8   import java.util.LinkedHashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  import java.util.TreeSet;
13  
14  import org.djunits.unit.FrequencyUnit;
15  import org.djunits.unit.SpeedUnit;
16  import org.djunits.value.vdouble.scalar.Duration;
17  import org.djunits.value.vdouble.scalar.Frequency;
18  import org.djunits.value.vdouble.scalar.Length;
19  import org.djunits.value.vdouble.scalar.Speed;
20  import org.djunits.value.vdouble.scalar.Time;
21  import org.djutils.exceptions.Throw;
22  import org.djutils.exceptions.Try;
23  import org.opentrafficsim.base.CompressedFileWriter;
24  import org.opentrafficsim.core.compatibility.Compatible;
25  import org.opentrafficsim.core.gtu.GTUDirectionality;
26  import org.opentrafficsim.core.gtu.GTUType;
27  import org.opentrafficsim.core.gtu.RelativePosition;
28  import org.opentrafficsim.core.network.NetworkException;
29  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
30  import org.opentrafficsim.road.network.OTSRoadNetwork;
31  import org.opentrafficsim.road.network.lane.CrossSectionElement;
32  import org.opentrafficsim.road.network.lane.Lane;
33  
34  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
35  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
36  import nl.tudelft.simulation.event.EventType;
37  
38  /**
39   * Detector, measuring a dynamic set of measurements.
40   * <p>
41   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
42   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
43   * <p>
44   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 5 mrt. 2018 <br>
45   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
46   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
47   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
48   */
49  public class Detector extends AbstractSensor
50  {
51  
52      /** */
53      private static final long serialVersionUID = 20180312L;
54  
55      /** Trigger event. Payload: [LaneBasedGTU gtu]. */
56      public static final EventType DETECTOR_TRIGGERED = new EventType("DUAL_LOOP_DETECTOR.TRIGGER");
57  
58      /** Aggregation event. Payload: [Frequency flow, other attached measurements]/ */
59      public static final EventType DETECTOR_AGGREGATE = new EventType("DUAL_LOOP_DETECTOR.AGGREGATE");
60  
61      /** Vehicles only compatibility. */
62      private static Compatible compatible = new Compatible()
63      {
64          /** {@inheritDoc} */
65          @Override
66          public boolean isCompatible(final GTUType gtuType, final GTUDirectionality directionality)
67          {
68              return gtuType.isOfType(gtuType.getNetwork().getGtuType(GTUType.DEFAULTS.VEHICLE));
69          }
70      };
71  
72      /** Mean speed measurement. */
73      public static final DetectorMeasurement<Double, Speed> MEAN_SPEED = new DetectorMeasurement<Double, Speed>()
74      {
75          @Override
76          public Double identity()
77          {
78              return 0.0;
79          }
80  
81          @Override
82          public Double accumulateEntry(final Double cumulative, final LaneBasedGTU gtu, final Detector loopDetector)
83          {
84              return cumulative + gtu.getSpeed().si;
85          }
86  
87          @Override
88          public Double accumulateExit(final Double cumulative, final LaneBasedGTU gtu, final Detector loopDetector)
89          {
90              return cumulative;
91          }
92  
93          @Override
94          public boolean isPeriodic()
95          {
96              return true;
97          }
98  
99          @Override
100         public Speed aggregate(final Double cumulative, final int count, final Duration aggregation)
101         {
102             return Speed.createSI(cumulative / count);
103         }
104 
105         @Override
106         public String getName()
107         {
108             return "v[km/h]";
109         }
110 
111         @Override
112         public String stringValue(final Speed aggregate, final String format)
113         {
114             return String.format(format, aggregate.getInUnit(SpeedUnit.KM_PER_HOUR));
115         }
116 
117         @Override
118         public String toString()
119         {
120             return getName();
121         }
122     };
123 
124     /** Harmonic mean speed measurement. */
125     public static final DetectorMeasurement<Double, Speed> HARMONIC_MEAN_SPEED = new DetectorMeasurement<Double, Speed>()
126     {
127         @Override
128         public Double identity()
129         {
130             return 0.0;
131         }
132 
133         @Override
134         public Double accumulateEntry(final Double cumulative, final LaneBasedGTU gtu, final Detector loopDetector)
135         {
136             return cumulative + (1.0 / gtu.getSpeed().si);
137         }
138 
139         @Override
140         public Double accumulateExit(final Double cumulative, final LaneBasedGTU gtu, final Detector loopDetector)
141         {
142             return cumulative;
143         }
144 
145         @Override
146         public boolean isPeriodic()
147         {
148             return true;
149         }
150 
151         @Override
152         public Speed aggregate(final Double cumulative, final int count, final Duration aggregation)
153         {
154             return Speed.createSI(count / cumulative);
155         }
156 
157         @Override
158         public String getName()
159         {
160             return "vHarm[km/h]";
161         }
162 
163         @Override
164         public String stringValue(final Speed aggregate, final String format)
165         {
166             return String.format(format, aggregate.getInUnit(SpeedUnit.KM_PER_HOUR));
167         }
168 
169         @Override
170         public String toString()
171         {
172             return getName();
173         }
174     };
175 
176     /** Occupancy measurement. */
177     public static final DetectorMeasurement<Double, Double> OCCUPANCY = new DetectorMeasurement<Double, Double>()
178     {
179         @Override
180         public Double identity()
181         {
182             return 0.0;
183         }
184 
185         @Override
186         public Double accumulateEntry(final Double cumulative, final LaneBasedGTU gtu, final Detector loopDetector)
187         {
188             return cumulative + ((gtu.getLength().si + loopDetector.getLength().si) / gtu.getSpeed().si);
189         }
190 
191         @Override
192         public Double accumulateExit(final Double cumulative, final LaneBasedGTU gtu, final Detector loopDetector)
193         {
194             return cumulative;
195         }
196 
197         @Override
198         public boolean isPeriodic()
199         {
200             return true;
201         }
202 
203         @Override
204         public Double aggregate(final Double cumulative, final int count, final Duration aggregation)
205         {
206             return cumulative / aggregation.si;
207         }
208 
209         @Override
210         public String getName()
211         {
212             return "occupancy";
213         }
214 
215         @Override
216         public String stringValue(final Double aggregate, final String format)
217         {
218             return String.format(format, aggregate);
219         }
220 
221         @Override
222         public String toString()
223         {
224             return getName();
225         }
226     };
227 
228     /** Passages measurement. */
229     public static final DetectorMeasurement<List<Double>, List<Double>> PASSAGES =
230             new DetectorMeasurement<List<Double>, List<Double>>()
231             {
232                 @Override
233                 public List<Double> identity()
234                 {
235                     return new ArrayList<>();
236                 }
237 
238                 @Override
239                 public List<Double> accumulateEntry(final List<Double> cumulative, final LaneBasedGTU gtu,
240                         final Detector loopDetector)
241                 {
242                     cumulative.add(gtu.getSimulator().getSimulatorTime().si);
243                     return cumulative;
244                 }
245 
246                 @Override
247                 public List<Double> accumulateExit(final List<Double> cumulative, final LaneBasedGTU gtu,
248                         final Detector loopDetector)
249                 {
250                     return cumulative;
251                 }
252 
253                 @Override
254                 public boolean isPeriodic()
255                 {
256                     return false;
257                 }
258 
259                 @Override
260                 public List<Double> aggregate(final List<Double> cumulative, final int count, final Duration aggregation)
261                 {
262                     return cumulative;
263                 }
264 
265                 @Override
266                 public String getName()
267                 {
268                     return "passage times";
269                 }
270 
271                 @Override
272                 public String stringValue(final List<Double> aggregate, final String format)
273                 {
274                     return printListDouble(aggregate, format);
275                 }
276 
277                 @Override
278                 public String toString()
279                 {
280                     return getName();
281                 }
282             };
283 
284     /** Aggregation time. */
285     private final Duration aggregation;
286 
287     /** Count per aggregation period. */
288     private final List<Frequency> count = new ArrayList<>();
289 
290     /** Measurements per aggregation period. */
291     private final Map<DetectorMeasurement<?, ?>, List<?>> dataMap = new LinkedHashMap<>();
292 
293     /** Detector length. */
294     private final Length length;
295 
296     /** Period number. */
297     private int period = 1;
298 
299     /** Count in current period. */
300     private int periodCount = 0;
301 
302     /** Count overall. */
303     private int overallCount = 0;
304 
305     /** Cumulative measurements. */
306     private final Map<DetectorMeasurement<?, ?>, Object> cumulDataMap = new LinkedHashMap<>();
307 
308     /**
309      * Constructor for regular Dutch dual-loop detectors measuring flow and mean speed aggregated over 60s.
310      * @param id String; detector id
311      * @param lane Lane; lane
312      * @param longitudinalPosition Length; position
313      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
314      * @throws NetworkException on network exception
315      */
316     public Detector(final String id, final Lane lane, final Length longitudinalPosition,
317             final DEVSSimulatorInterface.TimeDoubleUnit simulator) throws NetworkException
318     {
319         // Note: length not important for flow and mean speed
320         this(id, lane, longitudinalPosition, Length.ZERO, simulator, Duration.createSI(60.0), MEAN_SPEED);
321     }
322 
323     /**
324      * Constructor.
325      * @param id String; detector id
326      * @param lane Lane; lane
327      * @param longitudinalPosition Length; position
328      * @param length Length; length
329      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
330      * @param aggregation Duration; aggregation period
331      * @param measurements DetectorMeasurement&lt;?, ?&gt;...; measurements to obtain
332      * @throws NetworkException on network exception
333      */
334     public Detector(final String id, final Lane lane, final Length longitudinalPosition, final Length length,
335             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final Duration aggregation,
336             final DetectorMeasurement<?, ?>... measurements) throws NetworkException
337     {
338         super(id, lane, longitudinalPosition, RelativePosition.FRONT, simulator, compatible);
339         Throw.when(aggregation.si <= 0.0, IllegalArgumentException.class, "Aggregation time should be positive.");
340         this.length = length;
341         this.aggregation = aggregation;
342         Try.execute(() -> simulator.scheduleEventAbs(Time.createSI(aggregation.si), this, this, "aggregate", null), "");
343         for (DetectorMeasurement<?, ?> measurement : measurements)
344         {
345             this.cumulDataMap.put(measurement, measurement.identity());
346             if (measurement.isPeriodic())
347             {
348                 this.dataMap.put(measurement, new ArrayList<>());
349             }
350         }
351 
352         // rear detector
353         /** Abstract sensor. */
354         class RearDetector extends AbstractSensor
355         {
356             /** */
357             private static final long serialVersionUID = 20180315L;
358 
359             /**
360              * Constructor.
361              * @param idRear String; id
362              * @param laneRear Lane; lane
363              * @param longitudinalPositionRear Length; position
364              * @param simulatorRear DEVSSimulatorInterface.TimeDoubleUnit; simulator
365              * @throws NetworkException on network exception
366              */
367             @SuppressWarnings("synthetic-access")
368             RearDetector(final String idRear, final Lane laneRear, final Length longitudinalPositionRear,
369                     final DEVSSimulatorInterface.TimeDoubleUnit simulatorRear) throws NetworkException
370             {
371                 super(idRear, laneRear, longitudinalPositionRear, RelativePosition.REAR, simulatorRear, compatible);
372             }
373 
374             /** {@inheritDoc} */
375             @SuppressWarnings("synthetic-access")
376             @Override
377             protected void triggerResponse(final LaneBasedGTU gtu)
378             {
379                 for (DetectorMeasurement<?, ?> measurement : Detector.this.cumulDataMap.keySet())
380                 {
381                     accumulate(measurement, gtu, false);
382                 }
383             }
384 
385             /** {@inheritDoc} */
386             @Override
387             public AbstractSensor clone(final CrossSectionElement newCSE, final SimulatorInterface.TimeDoubleUnit newSimulator)
388                     throws NetworkException
389             {
390                 return null; // Detector constructor creates new clone
391             }
392         }
393         Length position = longitudinalPosition.plus(length);
394         Throw.when(position.gt(lane.getLength()), IllegalStateException.class,
395                 "A Detector can not be placed at a lane boundary");
396         new RearDetector(id + "_rear", lane, position, simulator);
397     }
398 
399     /**
400      * Returns the detector length.
401      * @return Length; the detector length
402      */
403     public Length getLength()
404     {
405         return this.length;
406     }
407 
408     /** {@inheritDoc} */
409     @Override
410     protected void triggerResponse(final LaneBasedGTU gtu)
411     {
412         this.periodCount++;
413         this.overallCount++;
414         for (DetectorMeasurement<?, ?> measurement : this.cumulDataMap.keySet())
415         {
416             accumulate(measurement, gtu, true);
417         }
418         if (this.listeners.containsKey(DETECTOR_TRIGGERED))
419         {
420             this.fireTimedEvent(DETECTOR_TRIGGERED, new Object[] {gtu}, getSimulator().getSimulatorTime());
421         }
422     }
423 
424     /**
425      * Accumulates a measurement.
426      * @param measurement DetectorMeasurement&lt;C, ?&gt;; measurement to accumulate
427      * @param gtu LaneBasedGTU; gtu
428      * @param front boolean; triggered by front entering (or rear leaving when false)
429      * @param <C> accumulated type
430      */
431     @SuppressWarnings("unchecked")
432     <C> void accumulate(final DetectorMeasurement<C, ?> measurement, final LaneBasedGTU gtu, final boolean front)
433     {
434         if (front)
435         {
436             this.cumulDataMap.put(measurement, measurement.accumulateEntry((C) this.cumulDataMap.get(measurement), gtu, this));
437         }
438         else
439         {
440             this.cumulDataMap.put(measurement, measurement.accumulateExit((C) this.cumulDataMap.get(measurement), gtu, this));
441         }
442     }
443 
444     /**
445      * Aggregation.
446      */
447     @SuppressWarnings("unused") // scheduled
448     private void aggregate()
449     {
450         Frequency frequency = Frequency.createSI(this.periodCount / this.aggregation.si);
451         this.count.add(frequency);
452         for (DetectorMeasurement<?, ?> measurement : this.dataMap.keySet())
453         {
454             aggregate(measurement, this.periodCount, this.aggregation);
455             this.cumulDataMap.put(measurement, measurement.identity());
456         }
457         this.periodCount = 0;
458         if (this.listeners.containsKey(DETECTOR_AGGREGATE))
459         {
460             Object[] data = new Object[this.dataMap.size() + 1];
461             data[0] = frequency;
462             int i = 1;
463             for (DetectorMeasurement<?, ?> measurement : this.dataMap.keySet())
464             {
465                 List<?> list = this.dataMap.get(measurement);
466                 data[i] = list.get(list.size() - 1);
467                 i++;
468             }
469             this.fireTimedEvent(DETECTOR_AGGREGATE, data, getSimulator().getSimulatorTime());
470         }
471         this.period++;
472         double t = this.aggregation.si * this.period;
473         Time time = Time.createSI(t);
474         Try.execute(() -> getSimulator().scheduleEventAbs(time, this, this, "aggregate", null), "");
475     }
476 
477     /**
478      * Returns the last flow.
479      * @return last flow
480      */
481     public Frequency getLastFlow()
482     {
483         return this.count.get(this.count.size() - 1);
484     }
485 
486     /**
487      * Returns the last value of the detector measurement.
488      * @param detectorMeasurement DetectorMeasurement&lt;?,A&gt;; detector measurement
489      * @return last value of the detector measurement
490      * @param <A> aggregate value type of the detector measurement
491      */
492     public <A> A getLastValue(final DetectorMeasurement<?, A> detectorMeasurement)
493     {
494         @SuppressWarnings("unchecked")
495         List<A> list = (List<A>) this.dataMap.get(detectorMeasurement);
496         return list.get(list.size() - 1);
497     }
498 
499     /**
500      * Aggregates a periodic measurement.
501      * @param measurement DetectorMeasurement&lt;C, A&gt;; measurement to aggregate
502      * @param cnt int; number of GTUs
503      * @param agg Duration; aggregation period
504      * @param <C> accumulated type
505      * @param <A> aggregated type
506      */
507     @SuppressWarnings("unchecked")
508     private <C, A> void aggregate(final DetectorMeasurement<C, A> measurement, final int cnt, final Duration agg)
509     {
510         ((List<A>) this.dataMap.get(measurement)).add(getAggregateValue(measurement, cnt, agg));
511     }
512 
513     /**
514      * Returns the aggregated value of the measurement.
515      * @param measurement DetectorMeasurement&lt;C, A&gt;; measurement to aggregate
516      * @param cnt int; number of GTUs
517      * @param agg Duration; aggregation period
518      * @return A; aggregated value of the measurement
519      * @param <C> accumulated type
520      * @param <A> aggregated type
521      */
522     @SuppressWarnings("unchecked")
523     private <C, A> A getAggregateValue(final DetectorMeasurement<C, A> measurement, final int cnt, final Duration agg)
524     {
525         return measurement.aggregate((C) this.cumulDataMap.get(measurement), cnt, agg);
526     }
527 
528     /**
529      * Returns a map of non-periodical measurements.
530      * @return Map&lt;DetectorMeasurement, Object&gt;; map of non-periodical measurements
531      */
532     private Map<DetectorMeasurement<?, ?>, Object> getMesoMeasurements()
533     {
534         Map<DetectorMeasurement<?, ?>, Object> map = new LinkedHashMap<>();
535         for (DetectorMeasurement<?, ?> measurement : this.cumulDataMap.keySet())
536         {
537             if (!measurement.isPeriodic())
538             {
539                 map.put(measurement, getAggregateValue(measurement, this.overallCount,
540                         this.getSimulator().getSimulatorTime().minus(Time.ZERO)));
541             }
542         }
543         return map;
544     }
545 
546     /** {@inheritDoc} */
547     @Override
548     public AbstractSensor clone(final CrossSectionElement newCSE, final SimulatorInterface.TimeDoubleUnit newSimulator)
549             throws NetworkException
550     {
551         return null;
552     }
553 
554     /**
555      * Write the contents of all detectors in to a file.
556      * @param network OTSRoadNetwork; network
557      * @param file String; file
558      * @param periodic boolean; periodic data
559      */
560     public static final void writeToFile(final OTSRoadNetwork network, final String file, final boolean periodic)
561     {
562         writeToFile(network, file, periodic, "%.3f", CompressionMethod.ZIP);
563     }
564 
565     /**
566      * Write the contents of all detectors in to a file.
567      * @param network OTSRoadNetwork; network
568      * @param file String; file
569      * @param periodic boolean; periodic data
570      * @param format String; number format, as used in {@code String.format()}
571      * @param compression CompressionMethod; how to compress the data
572      * @param <C> accumulated type
573      */
574     @SuppressWarnings("unchecked")
575     public static final <C> void writeToFile(final OTSRoadNetwork network, final String file, final boolean periodic,
576             final String format, final CompressionMethod compression)
577     {
578         BufferedWriter bw = CompressedFileWriter.create(file, compression.equals(CompressionMethod.ZIP));
579         try
580         {
581             // gather all DetectorMeasurements and Detectors (sorted)
582             Set<DetectorMeasurement<?, ?>> measurements = new LinkedHashSet<>();
583             Set<Detector> detectors = new TreeSet<>(new Comparator<Detector>()
584             {
585                 @Override
586                 public int compare(final Detector o1, final Detector o2)
587                 {
588                     return o1.getId().compareTo(o2.getId());
589                 }
590             });
591             for (Detector detector : network.getObjectMap(Detector.class).values())
592             {
593                 detectors.add(detector);
594             }
595             for (Detector detector : detectors)
596             {
597                 for (DetectorMeasurement<?, ?> measurement : detector.cumulDataMap.keySet())
598                 {
599                     if (measurement.isPeriodic() == periodic)
600                     {
601                         measurements.add(measurement);
602                     }
603                 }
604             }
605             // create headerline
606             StringBuilder str = periodic ? new StringBuilder("id,t[s],q[veh/h]") : new StringBuilder("id,measurement,data");
607             if (periodic)
608             {
609                 for (DetectorMeasurement<?, ?> measurement : measurements)
610                 {
611                     str.append(",");
612                     str.append(measurement.getName());
613                 }
614             }
615             bw.write(str.toString());
616             bw.newLine();
617             // create data lines
618             for (Detector detector : detectors)
619             {
620                 String id = detector.getId();
621                 // meso
622                 if (!periodic)
623                 {
624                     Map<DetectorMeasurement<?, ?>, Object> map = detector.getMesoMeasurements();
625                     for (DetectorMeasurement<?, ?> measurement : measurements)
626                     {
627                         if (map.containsKey(measurement))
628                         {
629                             bw.write(id + "," + measurement.getName() + ","
630                                     + ((DetectorMeasurement<?, C>) measurement).stringValue((C) map.get(measurement), format));
631                             bw.newLine();
632                         }
633                     }
634                 }
635                 else
636                 {
637                     // periodic
638                     double t = 0.0;
639                     for (int i = 0; i < detector.count.size(); i++)
640                     {
641                         str = new StringBuilder(id + "," + removeTrailingZeros(String.format(format, t)) + ",");
642                         str.append(removeTrailingZeros(
643                                 String.format(format, detector.count.get(i).getInUnit(FrequencyUnit.PER_HOUR))));
644                         for (DetectorMeasurement<?, ?> measurement : measurements)
645                         {
646                             str.append(",");
647                             List<?> list = detector.dataMap.get(measurement);
648                             if (list != null)
649                             {
650                                 str.append(removeTrailingZeros(
651                                         ((DetectorMeasurement<?, C>) measurement).stringValue((C) list.get(i), format)));
652                             }
653                         }
654                         bw.write(str.toString());
655                         bw.newLine();
656                         t += detector.aggregation.si;
657                     }
658                 }
659             }
660         }
661         catch (IOException exception)
662         {
663             throw new RuntimeException("Could not write to file.", exception);
664         }
665         // close file on fail
666         finally
667         {
668             try
669             {
670                 if (bw != null)
671                 {
672                     bw.close();
673                 }
674             }
675             catch (IOException ex)
676             {
677                 ex.printStackTrace();
678             }
679         }
680     }
681 
682     /**
683      * Remove any trailing zeros.
684      * @param string String; string of number
685      * @return String; string without trailing zeros
686      */
687     public static final String removeTrailingZeros(final String string)
688     {
689         return string.replaceFirst("\\.0*$|(\\.\\d*?)0+$", "$1");
690     }
691 
692     /**
693      * Prints a list of doubles in to a formatted string.
694      * @param list List&lt;Double&gt;; double values
695      * @param format String; format string
696      * @return formatted string of doubles
697      */
698     public static final String printListDouble(final List<Double> list, final String format)
699     {
700         StringBuilder str = new StringBuilder("[");
701         String sep = "";
702         for (double t : list)
703         {
704             str.append(sep);
705             str.append(removeTrailingZeros(String.format(format, t)));
706             sep = ", ";
707         }
708         str.append("]");
709         return str.toString();
710     }
711 
712     /**
713      * Defines the compression method for stored data.
714      * <p>
715      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
716      * <br>
717      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
718      * <p>
719      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 3 mei 2017 <br>
720      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
721      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
722      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
723      */
724     public enum CompressionMethod
725     {
726         /** No compression. */
727         NONE,
728 
729         /** Zip compression. */
730         ZIP,
731     }
732 
733     /**
734      * Interface for what detectors measure.
735      * <p>
736      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
737      * <br>
738      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
739      * <p>
740      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 12 mrt. 2018 <br>
741      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
742      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
743      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
744      * @param <C> accumulated type
745      * @param <A> aggregated type
746      */
747     public interface DetectorMeasurement<C, A>
748     {
749         /**
750          * Returns the initial value before accumulation.
751          * @return C; initial value before accumulation
752          */
753         C identity();
754 
755         /**
756          * Returns an accumulated value for when the front reaches the detector.
757          * @param cumulative C; accumulated value
758          * @param gtu LaneBasedGTU; gtu
759          * @param loopDetector Detector; loop detector
760          * @return C; accumulated value
761          */
762         C accumulateEntry(C cumulative, LaneBasedGTU gtu, Detector loopDetector);
763 
764         /**
765          * Returns an accumulated value for when the rear leaves the detector. GTU's may trigger an exit without having
766          * triggered an entry due to a lane change. Reversely, GTU's may not trigger an exit while they did trigger an entry.
767          * @param cumulative C; accumulated value
768          * @param gtu LaneBasedGTU; gtu
769          * @param loopDetector Detector; loop detector
770          * @return C; accumulated value
771          */
772         C accumulateExit(C cumulative, LaneBasedGTU gtu, Detector loopDetector);
773 
774         /**
775          * Returns whether the measurement aggregates every aggregation period (or only over the entire simulation).
776          * @return boolean; whether the measurement aggregates every aggregation period (or only over the entire simulation)
777          */
778         boolean isPeriodic();
779 
780         /**
781          * Returns an aggregated value.
782          * @param cumulative C; accumulated value
783          * @param count int; GTU count
784          * @param aggregation Duration; aggregation period
785          * @return A; aggregated value
786          */
787         A aggregate(C cumulative, int count, Duration aggregation);
788 
789         /**
790          * Returns the value name.
791          * @return String; value name
792          */
793         String getName();
794 
795         /**
796          * Returns a string representation of the aggregate result.
797          * @param aggregate A; aggregate result
798          * @param format String; format string
799          * @return String; string representation of the aggregate result
800          */
801         String stringValue(A aggregate, String format);
802     }
803 
804     /**
805      * Measurement of platoon sizes based on time between previous GTU exit and GTU entry.
806      * <p>
807      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
808      * <br>
809      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
810      * <p>
811      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 15 mrt. 2018 <br>
812      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
813      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
814      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
815      */
816     public static class PlatoonSizes implements DetectorMeasurement<PlatoonMeasurement, List<Integer>>
817     {
818 
819         /** Maximum time between two vehicles that are considered to be in the same platoon. */
820         private final Duration threshold;
821 
822         /**
823          * Constructor.
824          * @param threshold Duration; maximum time between two vehicles that are considered to be in the same platoon
825          */
826         public PlatoonSizes(final Duration threshold)
827         {
828             this.threshold = threshold;
829         }
830 
831         /** {@inheritDoc} */
832         @Override
833         public PlatoonMeasurement identity()
834         {
835             return new PlatoonMeasurement();
836         }
837 
838         /** {@inheritDoc} */
839         @SuppressWarnings("synthetic-access")
840         @Override
841         public PlatoonMeasurement accumulateEntry(final PlatoonMeasurement cumulative, final LaneBasedGTU gtu,
842                 final Detector loopDetector)
843         {
844             Time now = gtu.getSimulator().getSimulatorTime();
845             if (now.si - cumulative.lastExitTime.si < this.threshold.si)
846             {
847                 cumulative.count++;
848             }
849             else
850             {
851                 if (cumulative.count > 0) // 0 means this is the first vehicle of the first platoon
852                 {
853                     cumulative.platoons.add(cumulative.count);
854                 }
855                 cumulative.count = 1;
856             }
857             cumulative.enteredGTUs.add(gtu);
858             cumulative.lastExitTime = now; // should we change lane before triggering the exit
859             return cumulative;
860         }
861 
862         /** {@inheritDoc} */
863         @SuppressWarnings("synthetic-access")
864         @Override
865         public PlatoonMeasurement accumulateExit(final PlatoonMeasurement cumulative, final LaneBasedGTU gtu,
866                 final Detector loopDetector)
867         {
868             int index = cumulative.enteredGTUs.indexOf(gtu);
869             if (index >= 0)
870             {
871                 cumulative.lastExitTime = gtu.getSimulator().getSimulatorTime();
872                 // gtu is likely the oldest gtu in the list at index 0, but sometimes an older gtu may have left the detector by
873                 // changing lane, by clearing up to this gtu, older gtu's are automatically removed
874                 cumulative.enteredGTUs.subList(0, index).clear();
875             }
876             return cumulative;
877         }
878 
879         /** {@inheritDoc} */
880         @Override
881         public boolean isPeriodic()
882         {
883             return false;
884         }
885 
886         /** {@inheritDoc} */
887         @SuppressWarnings("synthetic-access")
888         @Override
889         public List<Integer> aggregate(final PlatoonMeasurement cumulative, final int count, final Duration aggregation)
890         {
891             if (cumulative.count > 0)
892             {
893                 cumulative.platoons.add(cumulative.count);
894                 cumulative.count = 0; // prevent that the last platoon is added again if the same output is saved again
895             }
896             return cumulative.platoons;
897         }
898 
899         /** {@inheritDoc} */
900         @Override
901         public String getName()
902         {
903             return "platoon sizes";
904         }
905 
906         /** {@inheritDoc} */
907         @Override
908         public String stringValue(final List<Integer> aggregate, final String format)
909         {
910             return aggregate.toString();
911         }
912 
913         /** {@inheritDoc} */
914         @Override
915         public String toString()
916         {
917             return getName();
918         }
919 
920     }
921 
922     /**
923      * Cumulative information for platoon size measurement.
924      * <p>
925      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
926      * <br>
927      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
928      * <p>
929      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 15 mrt. 2018 <br>
930      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
931      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
932      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
933      */
934     static class PlatoonMeasurement
935     {
936         /** GTU's counted so far in the current platoon. */
937         private int count = 0;
938 
939         /** Time the last GTU exited the detector. */
940         private Time lastExitTime = Time.createSI(Double.NEGATIVE_INFINITY);
941 
942         /** Stored sizes of earlier platoons. */
943         private List<Integer> platoons = new ArrayList<>();
944 
945         /** GTU's currently on the detector, some may have left by a lane change. */
946         private List<LaneBasedGTU> enteredGTUs = new ArrayList<>();
947     }
948 
949 }