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
42
43
44
45
46
47
48
49
50
51 public class Detector extends AbstractSensor
52 {
53
54
55 private static final long serialVersionUID = 20180312L;
56
57
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
63 public static final TimedEventType DETECTOR_AGGREGATE =
64 new TimedEventType("DUAL_LOOP_DETECTOR.AGGREGATE", MetaData.NO_META_DATA);
65
66
67 private static Compatible compatible = new Compatible()
68 {
69
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
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
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
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
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
290 private final Duration aggregation;
291
292
293 private final List<Frequency> count = new ArrayList<>();
294
295
296 private final Map<DetectorMeasurement<?, ?>, List<?>> dataMap = new LinkedHashMap<>();
297
298
299 private final Length length;
300
301
302 private int period = 1;
303
304
305 private int periodCount = 0;
306
307
308 private int overallCount = 0;
309
310
311 private final Map<DetectorMeasurement<?, ?>, Object> cumulDataMap = new LinkedHashMap<>();
312
313
314
315
316
317
318
319
320
321 public Detector(final String id, final Lane lane, final Length longitudinalPosition,
322 final DEVSSimulatorInterface.TimeDoubleUnit simulator) throws NetworkException
323 {
324
325 this(id, lane, longitudinalPosition, Length.ZERO, simulator, Duration.instantiateSI(60.0), MEAN_SPEED);
326 }
327
328
329
330
331
332
333
334
335
336
337
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
358
359 class RearDetector extends AbstractSensor
360 {
361
362 private static final long serialVersionUID = 20180315L;
363
364
365
366
367
368
369
370
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
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
391 @Override
392 public AbstractSensor clone(final CrossSectionElement newCSE, final SimulatorInterface.TimeDoubleUnit newSimulator)
393 throws NetworkException
394 {
395 return null;
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
406
407
408 public Length getLength()
409 {
410 return this.length;
411 }
412
413
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
428
429
430
431
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
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
480
481
482 public boolean hasLastValue()
483 {
484 return !this.count.isEmpty();
485 }
486
487
488
489
490
491 public Frequency getLastFlow()
492 {
493 return this.count.get(this.count.size() - 1);
494 }
495
496
497
498
499
500
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
511
512
513
514
515
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
525
526
527
528
529
530
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
540
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
557 @Override
558 public AbstractSensor clone(final CrossSectionElement newCSE, final SimulatorInterface.TimeDoubleUnit newSimulator)
559 throws NetworkException
560 {
561
562 return null;
563 }
564
565
566
567
568
569
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
578
579
580
581
582
583
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
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
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
629 for (Detector detector : detectors)
630 {
631 String id = detector.getFullId();
632
633 if (!periodic)
634 {
635 Map<DetectorMeasurement<?, ?>, Object> map = detector.getMesoMeasurements();
636 for (DetectorMeasurement<?, ?> measurement : measurements)
637 {
638 if (map.containsKey(measurement))
639 {
640
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
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
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
697
698
699
700 public static final String removeTrailingZeros(final String string)
701 {
702 return string.replaceFirst("\\.0*$|(\\.\\d*?)0+$", "$1");
703 }
704
705
706
707
708
709
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
727
728
729
730
731
732
733
734
735
736
737 public enum CompressionMethod
738 {
739
740 NONE,
741
742
743 ZIP,
744 }
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760 public interface DetectorMeasurement<C, A>
761 {
762
763
764
765
766 C identity();
767
768
769
770
771
772
773
774
775
776 C accumulateEntry(C cumulative, LaneBasedGTU gtu, Detector loopDetector);
777
778
779
780
781
782
783
784
785
786 C accumulateExit(C cumulative, LaneBasedGTU gtu, Detector loopDetector);
787
788
789
790
791
792 boolean isPeriodic();
793
794
795
796
797
798
799
800
801 A aggregate(C cumulative, int count, Duration aggregation);
802
803
804
805
806
807 String getName();
808
809
810
811
812
813
814
815 String stringValue(A aggregate, String format);
816 }
817
818
819
820
821
822
823
824
825
826
827
828
829
830 public static class PlatoonSizes implements DetectorMeasurement<PlatoonMeasurement, List<Integer>>
831 {
832
833
834 private final Duration threshold;
835
836
837
838
839
840 public PlatoonSizes(final Duration threshold)
841 {
842 this.threshold = threshold;
843 }
844
845
846 @Override
847 public PlatoonMeasurement identity()
848 {
849 return new PlatoonMeasurement();
850 }
851
852
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)
866 {
867 cumulative.platoons.add(cumulative.count);
868 }
869 cumulative.count = 1;
870 }
871 cumulative.enteredGTUs.add(gtu);
872 cumulative.lastExitTime = now;
873 return cumulative;
874 }
875
876
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
887
888 cumulative.enteredGTUs.subList(0, index).clear();
889 }
890 return cumulative;
891 }
892
893
894 @Override
895 public boolean isPeriodic()
896 {
897 return false;
898 }
899
900
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;
909 }
910 return cumulative.platoons;
911 }
912
913
914 @Override
915 public String getName()
916 {
917 return "platoon sizes";
918 }
919
920
921 @Override
922 public String stringValue(final List<Integer> aggregate, final String format)
923 {
924 return aggregate.toString();
925 }
926
927
928 @Override
929 public String toString()
930 {
931 return getName();
932 }
933
934 }
935
936
937
938
939
940
941
942
943
944
945
946
947
948 static class PlatoonMeasurement
949 {
950
951 private int count = 0;
952
953
954 private Time lastExitTime = Time.instantiateSI(Double.NEGATIVE_INFINITY);
955
956
957 private List<Integer> platoons = new ArrayList<>();
958
959
960 private List<LaneBasedGTU> enteredGTUs = new ArrayList<>();
961 }
962
963 }