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
40
41
42
43
44
45
46
47
48
49 public class Detector extends AbstractSensor
50 {
51
52
53 private static final long serialVersionUID = 20180312L;
54
55
56 public static final EventType DETECTOR_TRIGGERED = new EventType("DUAL_LOOP_DETECTOR.TRIGGER");
57
58
59 public static final EventType DETECTOR_AGGREGATE = new EventType("DUAL_LOOP_DETECTOR.AGGREGATE");
60
61
62 private static Compatible compatible = new Compatible()
63 {
64
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
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
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
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
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
285 private final Duration aggregation;
286
287
288 private final List<Frequency> count = new ArrayList<>();
289
290
291 private final Map<DetectorMeasurement<?, ?>, List<?>> dataMap = new LinkedHashMap<>();
292
293
294 private final Length length;
295
296
297 private int period = 1;
298
299
300 private int periodCount = 0;
301
302
303 private int overallCount = 0;
304
305
306 private final Map<DetectorMeasurement<?, ?>, Object> cumulDataMap = new LinkedHashMap<>();
307
308
309
310
311
312
313
314
315
316 public Detector(final String id, final Lane lane, final Length longitudinalPosition,
317 final DEVSSimulatorInterface.TimeDoubleUnit simulator) throws NetworkException
318 {
319
320 this(id, lane, longitudinalPosition, Length.ZERO, simulator, Duration.createSI(60.0), MEAN_SPEED);
321 }
322
323
324
325
326
327
328
329
330
331
332
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
353
354 class RearDetector extends AbstractSensor
355 {
356
357 private static final long serialVersionUID = 20180315L;
358
359
360
361
362
363
364
365
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
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
386 @Override
387 public AbstractSensor clone(final CrossSectionElement newCSE, final SimulatorInterface.TimeDoubleUnit newSimulator)
388 throws NetworkException
389 {
390 return null;
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
401
402
403 public Length getLength()
404 {
405 return this.length;
406 }
407
408
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
426
427
428
429
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
446
447 @SuppressWarnings("unused")
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
479
480
481 public Frequency getLastFlow()
482 {
483 return this.count.get(this.count.size() - 1);
484 }
485
486
487
488
489
490
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
501
502
503
504
505
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
515
516
517
518
519
520
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
530
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
547 @Override
548 public AbstractSensor clone(final CrossSectionElement newCSE, final SimulatorInterface.TimeDoubleUnit newSimulator)
549 throws NetworkException
550 {
551 return null;
552 }
553
554
555
556
557
558
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
567
568
569
570
571
572
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
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
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
618 for (Detector detector : detectors)
619 {
620 String id = detector.getId();
621
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
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
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
684
685
686
687 public static final String removeTrailingZeros(final String string)
688 {
689 return string.replaceFirst("\\.0*$|(\\.\\d*?)0+$", "$1");
690 }
691
692
693
694
695
696
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
714
715
716
717
718
719
720
721
722
723
724 public enum CompressionMethod
725 {
726
727 NONE,
728
729
730 ZIP,
731 }
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747 public interface DetectorMeasurement<C, A>
748 {
749
750
751
752
753 C identity();
754
755
756
757
758
759
760
761
762 C accumulateEntry(C cumulative, LaneBasedGTU gtu, Detector loopDetector);
763
764
765
766
767
768
769
770
771
772 C accumulateExit(C cumulative, LaneBasedGTU gtu, Detector loopDetector);
773
774
775
776
777
778 boolean isPeriodic();
779
780
781
782
783
784
785
786
787 A aggregate(C cumulative, int count, Duration aggregation);
788
789
790
791
792
793 String getName();
794
795
796
797
798
799
800
801 String stringValue(A aggregate, String format);
802 }
803
804
805
806
807
808
809
810
811
812
813
814
815
816 public static class PlatoonSizes implements DetectorMeasurement<PlatoonMeasurement, List<Integer>>
817 {
818
819
820 private final Duration threshold;
821
822
823
824
825
826 public PlatoonSizes(final Duration threshold)
827 {
828 this.threshold = threshold;
829 }
830
831
832 @Override
833 public PlatoonMeasurement identity()
834 {
835 return new PlatoonMeasurement();
836 }
837
838
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)
852 {
853 cumulative.platoons.add(cumulative.count);
854 }
855 cumulative.count = 1;
856 }
857 cumulative.enteredGTUs.add(gtu);
858 cumulative.lastExitTime = now;
859 return cumulative;
860 }
861
862
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
873
874 cumulative.enteredGTUs.subList(0, index).clear();
875 }
876 return cumulative;
877 }
878
879
880 @Override
881 public boolean isPeriodic()
882 {
883 return false;
884 }
885
886
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;
895 }
896 return cumulative.platoons;
897 }
898
899
900 @Override
901 public String getName()
902 {
903 return "platoon sizes";
904 }
905
906
907 @Override
908 public String stringValue(final List<Integer> aggregate, final String format)
909 {
910 return aggregate.toString();
911 }
912
913
914 @Override
915 public String toString()
916 {
917 return getName();
918 }
919
920 }
921
922
923
924
925
926
927
928
929
930
931
932
933
934 static class PlatoonMeasurement
935 {
936
937 private int count = 0;
938
939
940 private Time lastExitTime = Time.createSI(Double.NEGATIVE_INFINITY);
941
942
943 private List<Integer> platoons = new ArrayList<>();
944
945
946 private List<LaneBasedGTU> enteredGTUs = new ArrayList<>();
947 }
948
949 }