1 package org.opentrafficsim.graphs;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.event.ActionEvent;
6 import java.awt.geom.Line2D;
7 import java.io.Serializable;
8 import java.rmi.RemoteException;
9 import java.util.ArrayList;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Set;
15
16 import javax.swing.JFrame;
17 import javax.swing.JLabel;
18 import javax.swing.JPopupMenu;
19 import javax.swing.SwingConstants;
20
21 import org.djunits.unit.LengthUnit;
22 import org.djunits.unit.TimeUnit;
23 import org.djunits.value.vdouble.scalar.DoubleScalar;
24 import org.djunits.value.vdouble.scalar.Duration;
25 import org.djunits.value.vdouble.scalar.Length;
26 import org.djunits.value.vdouble.scalar.Time;
27 import org.jfree.chart.ChartFactory;
28 import org.jfree.chart.ChartPanel;
29 import org.jfree.chart.JFreeChart;
30 import org.jfree.chart.StandardChartTheme;
31 import org.jfree.chart.axis.NumberAxis;
32 import org.jfree.chart.axis.ValueAxis;
33 import org.jfree.chart.plot.PlotOrientation;
34 import org.jfree.chart.plot.XYPlot;
35 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
36 import org.jfree.data.DomainOrder;
37 import org.jfree.data.general.DatasetChangeEvent;
38 import org.jfree.data.general.DatasetChangeListener;
39 import org.jfree.data.general.DatasetGroup;
40 import org.jfree.data.xy.XYDataset;
41 import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
42 import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
43 import org.opentrafficsim.core.gtu.GTUException;
44 import org.opentrafficsim.core.network.NetworkException;
45 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
46 import org.opentrafficsim.road.network.lane.Lane;
47
48 import nl.tudelft.simulation.dsol.SimRuntimeException;
49 import nl.tudelft.simulation.event.EventInterface;
50 import nl.tudelft.simulation.event.EventListenerInterface;
51 import nl.tudelft.simulation.event.TimedEvent;
52
53
54
55
56
57
58
59
60
61
62
63 public class TrajectoryPlot extends AbstractOTSPlot implements XYDataset, LaneBasedGTUSampler, EventListenerInterface
64
65 {
66
67 private static final long serialVersionUID = 20140724L;
68
69
70 private final Duration sampleInterval;
71
72
73 private final OTSDEVSSimulatorInterface simulator;
74
75
76
77
78
79 public final Duration getSampleInterval()
80 {
81 return this.sampleInterval;
82 }
83
84
85 private final double[] cumulativeLengths;
86
87
88
89
90
91
92 public final double getCumulativeLength(final int index)
93 {
94 return index == -1 ? this.cumulativeLengths[this.cumulativeLengths.length - 1] : this.cumulativeLengths[index];
95 }
96
97
98 private Time maximumTime = new Time(300, TimeUnit.BASE);
99
100
101
102
103 public final Time getMaximumTime()
104 {
105 return this.maximumTime;
106 }
107
108
109
110
111 public final void setMaximumTime(final Time maximumTime)
112 {
113 this.maximumTime = maximumTime;
114 }
115
116
117 private DatasetGroup datasetGroup = null;
118
119
120
121
122
123
124
125
126
127 public TrajectoryPlot(final String caption, final Duration sampleInterval, final List<Lane> path,
128 final OTSDEVSSimulatorInterface simulator)
129 {
130 super(caption, path);
131 this.sampleInterval = sampleInterval;
132 this.simulator = simulator;
133 double[] endLengths = new double[path.size()];
134 double cumulativeLength = 0;
135 for (int i = 0; i < path.size(); i++)
136 {
137 Lane lane = path.get(i);
138 lane.addListener(this, Lane.GTU_ADD_EVENT, true);
139 lane.addListener(this, Lane.GTU_REMOVE_EVENT, true);
140 try
141 {
142
143 for (LaneBasedGTU gtu : lane.getGtuList())
144 {
145 notify(new TimedEvent<OTSSimTimeDouble>(Lane.GTU_ADD_EVENT, lane, new Object[] { gtu.getId(), gtu },
146 gtu.getSimulator().getSimulatorTime()));
147 }
148 }
149 catch (RemoteException exception)
150 {
151 exception.printStackTrace();
152 }
153 cumulativeLength += lane.getLength().getSI();
154 endLengths[i] = cumulativeLength;
155 }
156 this.cumulativeLengths = endLengths;
157 setChart(createChart(this));
158 this.reGraph();
159 if (null != this.sampleInterval)
160 {
161 try
162 {
163 this.simulator.scheduleEventRel(Duration.ZERO, this, this, "sample", null);
164 }
165 catch (SimRuntimeException exception)
166 {
167 exception.printStackTrace();
168 }
169 }
170 }
171
172
173 @Override
174 public final GraphType getGraphType()
175 {
176 return GraphType.TRAJECTORY;
177 }
178
179
180
181
182 public final void sample()
183 {
184 Time now = this.simulator.getSimulatorTime().getTime();
185 for (LaneBasedGTU gtu : this.gtusOfInterest)
186 {
187 try
188 {
189 Map<Lane, Length> positions = gtu.positions(gtu.getReference(), now);
190 int hits = 0;
191 for (Lane lane : positions.keySet())
192 {
193 if (getPath().contains(lane))
194 {
195 Length position = positions.get(lane);
196 if (position.si >= 0 && position.si <= lane.getLength().si)
197 {
198 addData(gtu, lane, positions.get(lane).si);
199 hits++;
200 }
201 }
202 }
203 if (1 != hits)
204 {
205 System.err.println("GTU " + gtu + " scored " + hits + " (expected 1 hit)");
206 }
207 }
208 catch (GTUException exception)
209 {
210 exception.printStackTrace();
211 }
212 }
213
214 try
215 {
216 this.simulator.scheduleEventRel(this.sampleInterval, this, this, "sample", null);
217 }
218 catch (SimRuntimeException exception)
219 {
220 exception.printStackTrace();
221 }
222 }
223
224
225 private Set<LaneBasedGTU> gtusOfInterest = new HashSet<>();
226
227
228 @Override
229 @SuppressWarnings("checkstyle:designforextension")
230 public void notify(final EventInterface event) throws RemoteException
231 {
232 LaneBasedGTU gtu;
233 if (event.getType().equals(Lane.GTU_ADD_EVENT))
234 {
235 Object[] content = (Object[]) event.getContent();
236 gtu = (LaneBasedGTU) content[1];
237 if (!this.gtusOfInterest.contains(gtu))
238 {
239 this.gtusOfInterest.add(gtu);
240 if (null == this.sampleInterval)
241 {
242 gtu.addListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT);
243 }
244 }
245 }
246 else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
247 {
248 Object[] content = (Object[]) event.getContent();
249 gtu = (LaneBasedGTU) content[1];
250 Lane lane = null;
251 try
252 {
253 lane = gtu.getReferencePosition().getLane();
254 }
255 catch (GTUException exception)
256 {
257
258 }
259 if (lane == null || !getPath().contains(lane))
260 {
261 this.gtusOfInterest.remove(gtu);
262 if (null != this.sampleInterval)
263 {
264 gtu.removeListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT);
265 }
266 else
267 {
268 String key = gtu.getId();
269 VariableSampleRateTrajectory carTrajectory = (VariableSampleRateTrajectory) this.trajectories.get(key);
270 if (null != carTrajectory)
271 {
272 carTrajectory.recordGTULeftTrajectoryEvent();
273 }
274 }
275 }
276 }
277 else if (event.getType().equals(LaneBasedGTU.LANEBASED_MOVE_EVENT))
278 {
279 Object[] content = (Object[]) event.getContent();
280 Lane lane = (Lane) content[6];
281 Length posOnLane = (Length) content[7];
282 gtu = (LaneBasedGTU) event.getSource();
283 if (getPath().contains(lane))
284 {
285 addData(gtu, lane, posOnLane.si);
286 }
287 }
288 }
289
290
291 @Override
292 protected final JFreeChart createChart(final JFrame container)
293 {
294 final JLabel statusLabel = new JLabel(" ", SwingConstants.CENTER);
295 container.add(statusLabel, BorderLayout.SOUTH);
296 ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
297 final JFreeChart result =
298 ChartFactory.createXYLineChart(getCaption(), "", "", this, PlotOrientation.VERTICAL, false, false, false);
299
300 result.getPlot().setBackgroundPaint(new Color(0.9f, 0.9f, 0.9f));
301 FixCaption.fixCaption(result);
302 NumberAxis xAxis = new NumberAxis("\u2192 " + "time [s]");
303 xAxis.setLowerMargin(0.0);
304 xAxis.setUpperMargin(0.0);
305 NumberAxis yAxis = new NumberAxis("\u2192 " + "Distance [m]");
306 yAxis.setAutoRangeIncludesZero(false);
307 yAxis.setLowerMargin(0.0);
308 yAxis.setUpperMargin(0.0);
309 yAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
310 result.getXYPlot().setDomainAxis(xAxis);
311 result.getXYPlot().setRangeAxis(yAxis);
312 Length minimumPosition = Length.ZERO;
313 Length maximumPosition = new Length(getCumulativeLength(-1), LengthUnit.SI);
314 configureAxis(result.getXYPlot().getRangeAxis(), DoubleScalar.minus(maximumPosition, minimumPosition).getSI());
315 final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) result.getXYPlot().getRenderer();
316 renderer.setBaseLinesVisible(true);
317 renderer.setBaseShapesVisible(false);
318 renderer.setBaseShape(new Line2D.Float(0, 0, 0, 0));
319 final ChartPanel cp = new ChartPanel(result);
320 cp.setMouseWheelEnabled(true);
321 final PointerHandler ph = new PointerHandler()
322 {
323
324 @Override
325 void updateHint(final double domainValue, final double rangeValue)
326 {
327 if (Double.isNaN(domainValue))
328 {
329 statusLabel.setText(" ");
330 return;
331 }
332 String value = "";
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 statusLabel.setText(String.format("t=%.0fs, distance=%.0fm%s", domainValue, rangeValue, value));
394 }
395 };
396 cp.addMouseMotionListener(ph);
397 cp.addMouseListener(ph);
398 container.add(cp, BorderLayout.CENTER);
399
400
401 JPopupMenu popupMenu = cp.getPopupMenu();
402 popupMenu.add(new JPopupMenu.Separator());
403 popupMenu.add(StandAloneChartWindow.createMenuItem(this));
404 return result;
405 }
406
407
408 @Override
409 public final void reGraph()
410 {
411 for (DatasetChangeListener dcl : getListenerList().getListeners(DatasetChangeListener.class))
412 {
413 if (dcl instanceof XYPlot)
414 {
415 configureAxis(((XYPlot) dcl).getDomainAxis(), this.maximumTime.getSI());
416 }
417 }
418 notifyListeners(new DatasetChangeEvent(this, null));
419 }
420
421
422
423
424
425
426 private static void configureAxis(final ValueAxis valueAxis, final double range)
427 {
428 valueAxis.setUpperBound(range);
429 valueAxis.setLowerMargin(0);
430 valueAxis.setUpperMargin(0);
431 valueAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
432 valueAxis.setAutoRange(true);
433 valueAxis.setAutoRangeMinimumSize(range);
434 valueAxis.centerRange(range / 2);
435 }
436
437
438 @Override
439 public void actionPerformed(final ActionEvent e)
440 {
441
442 }
443
444
445 private HashMap<String, Trajectory> trajectories = new HashMap<String, Trajectory>();
446
447
448 private ArrayList<Trajectory> trajectoryIndices = new ArrayList<Trajectory>();
449
450
451
452
453
454
455
456 protected final void addData(final LaneBasedGTU gtu, final Lane lane, final double posOnLane)
457 {
458 int index = getPath().indexOf(lane);
459 if (index < 0)
460 {
461
462 System.err.println("TrajectoryPlot: GTU " + gtu.getId() + " is not registered on lane " + lane.toString());
463 return;
464 }
465 double lengthOffset = index == 0 ? 0 : this.cumulativeLengths[index - 1];
466
467 String key = gtu.getId();
468 Trajectory carTrajectory = this.trajectories.get(key);
469 if (null == carTrajectory)
470 {
471
472 carTrajectory =
473 null == this.sampleInterval ? new VariableSampleRateTrajectory(key) : new FixedSampleRateTrajectory(key);
474 this.trajectoryIndices.add(carTrajectory);
475 this.trajectories.put(key, carTrajectory);
476 }
477 try
478 {
479 carTrajectory.addSample(gtu, lane, lengthOffset + posOnLane);
480 }
481 catch (NetworkException | GTUException exception)
482 {
483
484 System.err.println("TrajectoryPlot: GTU " + gtu.getId() + " on lane " + lane.toString() + " caused exception "
485 + exception.getMessage());
486 }
487 }
488
489
490
491
492 interface Trajectory
493 {
494
495
496
497
498 Time getCurrentEndTime();
499
500
501
502
503
504 Double getLastPosition();
505
506
507
508
509
510 String getId();
511
512
513
514
515
516
517
518
519
520 void addSample(LaneBasedGTU gtu, Lane lane, double position) throws NetworkException, GTUException;
521
522
523
524
525
526 int size();
527
528
529
530
531
532
533 double getTime(int item);
534
535
536
537
538
539
540 double getDistance(int item);
541
542 }
543
544
545
546
547
548
549 class VariableSampleRateTrajectory implements Trajectory, Serializable
550 {
551
552 private static final long serialVersionUID = 20140000L;
553
554
555 private Time currentEndTime;
556
557
558 private final String id;
559
560
561 private ArrayList<DistanceAndTime> samples = new ArrayList<DistanceAndTime>();
562
563
564
565
566
567 VariableSampleRateTrajectory(final String id)
568 {
569 this.id = id;
570 }
571
572
573 @Override
574 public Time getCurrentEndTime()
575 {
576 return this.currentEndTime;
577 }
578
579
580 @Override
581 public Double getLastPosition()
582 {
583 return null;
584 }
585
586
587 @Override
588 public String getId()
589 {
590 return this.id;
591 }
592
593
594 @Override
595 public void addSample(final LaneBasedGTU gtu, final Lane lane, final double position)
596 throws NetworkException, GTUException
597 {
598 if (this.samples.size() > 0)
599 {
600 DistanceAndTime lastSample = this.samples.get(this.samples.size() - 1);
601 if (null != lastSample)
602 {
603 Double lastPosition = lastSample.getDistance();
604 if (null != lastPosition && Math.abs(lastPosition - position) > 0.9 * getCumulativeLength(-1))
605 {
606
607 recordGTULeftTrajectoryEvent();
608 }
609 }
610 }
611 this.currentEndTime = gtu.getSimulator().getSimulatorTime().getTime();
612 this.samples.add(new DistanceAndTime(position, this.currentEndTime.si));
613 if (gtu.getSimulator().getSimulatorTime().getTime().gt(getMaximumTime()))
614 {
615 setMaximumTime(gtu.getSimulator().getSimulatorTime().getTime());
616 }
617 }
618
619
620
621
622 public void recordGTULeftTrajectoryEvent()
623 {
624 this.samples.add(null);
625 }
626
627
628 @Override
629 public int size()
630 {
631 return this.samples.size();
632 }
633
634
635
636
637
638
639 private DistanceAndTime getSample(final int item)
640 {
641 return this.samples.get(item);
642 }
643
644
645 @Override
646 public double getTime(final int item)
647 {
648 DistanceAndTime sample = getSample(item);
649 if (null == sample)
650 {
651 return Double.NaN;
652 }
653 return this.samples.get(item).getTime();
654 }
655
656
657 @Override
658 public double getDistance(final int item)
659 {
660 DistanceAndTime sample = getSample(item);
661 if (null == sample)
662 {
663 return Double.NaN;
664 }
665 return sample.getDistance();
666 }
667
668
669 @Override
670 public String toString()
671 {
672 return "VariableSampleRateTrajectory [id=" + this.id + ", currentEndTime=" + this.currentEndTime + "]";
673 }
674
675
676
677
678 class DistanceAndTime
679 {
680
681 private final double distance;
682
683
684 private final double time;
685
686
687
688
689
690
691 DistanceAndTime(final double distance, final double time)
692 {
693 this.distance = distance;
694 this.time = time;
695 }
696
697
698
699
700
701 public double getDistance()
702 {
703 return this.distance;
704 }
705
706
707
708
709
710 public double getTime()
711 {
712 return this.time;
713 }
714
715
716 @Override
717 public String toString()
718 {
719 return "DistanceAndTime [distance=" + this.distance + ", time=" + this.time + "]";
720 }
721
722 }
723 }
724
725
726
727
728
729
730 class FixedSampleRateTrajectory implements Trajectory, Serializable
731 {
732
733 private static final long serialVersionUID = 20140000L;
734
735
736 private Time currentEndTime;
737
738
739 private final String id;
740
741
742 private ArrayList<Double> positions = new ArrayList<Double>();
743
744
745 private int firstSample;
746
747
748
749
750
751 FixedSampleRateTrajectory(final String id)
752 {
753 this.id = id;
754 }
755
756
757 public final Time getCurrentEndTime()
758 {
759 return this.currentEndTime;
760 }
761
762
763 public final Double getLastPosition()
764 {
765 for (int i = this.positions.size(); --i >= 0;)
766 {
767 Double result = this.positions.get(i);
768 if (null != result)
769 {
770 return result;
771 }
772 }
773 return null;
774 }
775
776
777 public final String getId()
778 {
779 return this.id;
780 }
781
782
783 public final void addSample(final LaneBasedGTU gtu, final Lane lane, final double position)
784 throws NetworkException, GTUException
785 {
786 final int sample = (int) Math.ceil(gtu.getOperationalPlan().getStartTime().si / getSampleInterval().si);
787 if (0 == this.positions.size())
788 {
789 this.firstSample = sample;
790 }
791 while (sample - this.firstSample > this.positions.size())
792 {
793
794 this.positions.add(null);
795 }
796 Double adjustedPosition = position;
797 Double lastPosition = this.positions.size() > 0 ? this.positions.get(this.positions.size() - 1) : null;
798 if (null != lastPosition && Math.abs(lastPosition - position) > 0.9 * getCumulativeLength(-1))
799 {
800
801 adjustedPosition = null;
802 }
803 this.positions.add(adjustedPosition);
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855 if (gtu.getSimulator().getSimulatorTime().getTime().gt(getMaximumTime()))
856 {
857 setMaximumTime(gtu.getSimulator().getSimulatorTime().getTime());
858 }
859 }
860
861
862 public int size()
863 {
864 return this.positions.size();
865 }
866
867
868 public double getTime(final int item)
869 {
870 return (item + this.firstSample) * getSampleInterval().si;
871 }
872
873
874
875
876
877 public double getDistance(final int item)
878 {
879 Double distance = this.positions.get(item);
880 if (null == distance)
881 {
882 return Double.NaN;
883 }
884 return this.positions.get(item);
885 }
886
887
888 @Override
889 public final String toString()
890 {
891 return "FixedSampleRateTrajectory [currentEndTime=" + this.currentEndTime + ", id=" + this.id + ", positions.size="
892 + this.positions.size() + ", firstSample=" + this.firstSample + "]";
893 }
894
895 }
896
897
898 @Override
899 public final int getSeriesCount()
900 {
901 return this.trajectories.size();
902 }
903
904
905 @Override
906 public final Comparable<Integer> getSeriesKey(final int series)
907 {
908 return series;
909 }
910
911
912 @SuppressWarnings("rawtypes")
913 @Override
914 public final int indexOf(final Comparable seriesKey)
915 {
916 if (seriesKey instanceof Integer)
917 {
918 return (Integer) seriesKey;
919 }
920 return -1;
921 }
922
923
924 @Override
925 public final DatasetGroup getGroup()
926 {
927 return this.datasetGroup;
928 }
929
930
931 @Override
932 public final void setGroup(final DatasetGroup group)
933 {
934 this.datasetGroup = group;
935 }
936
937
938 @Override
939 public final DomainOrder getDomainOrder()
940 {
941 return DomainOrder.ASCENDING;
942 }
943
944
945 @Override
946 public final int getItemCount(final int series)
947 {
948 return this.trajectoryIndices.get(series).size();
949 }
950
951
952 @Override
953 public final Number getX(final int series, final int item)
954 {
955 double v = getXValue(series, item);
956 if (Double.isNaN(v))
957 {
958 return null;
959 }
960 return v;
961 }
962
963
964 @Override
965 public final double getXValue(final int series, final int item)
966 {
967 return this.trajectoryIndices.get(series).getTime(item);
968 }
969
970
971 @Override
972 public final Number getY(final int series, final int item)
973 {
974 double v = getYValue(series, item);
975 if (Double.isNaN(v))
976 {
977 return null;
978 }
979 return v;
980 }
981
982
983 @Override
984 public final double getYValue(final int series, final int item)
985 {
986 return this.trajectoryIndices.get(series).getDistance(item);
987 }
988
989
990 @Override
991 public final String toString()
992 {
993 return "TrajectoryPlot [sampleInterval=" + this.sampleInterval + ", path=" + getPath() + ", cumulativeLengths.length="
994 + this.cumulativeLengths.length + ", maximumTime=" + this.maximumTime + ", caption=" + getCaption()
995 + ", trajectories.size=" + this.trajectories.size() + "]";
996 }
997
998 }