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