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