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.event.ActionListener;
7 import java.awt.geom.Line2D;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.List;
11
12 import javax.swing.JFrame;
13 import javax.swing.JLabel;
14 import javax.swing.JPopupMenu;
15 import javax.swing.SwingConstants;
16 import javax.swing.event.EventListenerList;
17
18 import org.djunits.unit.LengthUnit;
19 import org.djunits.unit.TimeUnit;
20 import org.djunits.value.StorageType;
21 import org.djunits.value.ValueException;
22 import org.djunits.value.vdouble.scalar.DoubleScalar;
23 import org.djunits.value.vdouble.scalar.Length;
24 import org.djunits.value.vdouble.scalar.Time;
25 import org.djunits.value.vdouble.vector.LengthVector;
26 import org.jfree.chart.ChartFactory;
27 import org.jfree.chart.ChartPanel;
28 import org.jfree.chart.JFreeChart;
29 import org.jfree.chart.StandardChartTheme;
30 import org.jfree.chart.axis.NumberAxis;
31 import org.jfree.chart.axis.ValueAxis;
32 import org.jfree.chart.plot.PlotOrientation;
33 import org.jfree.chart.plot.XYPlot;
34 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
35 import org.jfree.data.DomainOrder;
36 import org.jfree.data.general.DatasetChangeEvent;
37 import org.jfree.data.general.DatasetChangeListener;
38 import org.jfree.data.general.DatasetGroup;
39 import org.jfree.data.xy.XYDataset;
40 import org.opentrafficsim.core.gtu.GTUException;
41 import org.opentrafficsim.core.network.NetworkException;
42 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
43 import org.opentrafficsim.road.network.lane.Lane;
44
45
46
47
48
49
50
51
52
53
54
55 public class TrajectoryPlot extends JFrame implements ActionListener, XYDataset, MultipleViewerChart, LaneBasedGTUSampler
56
57 {
58
59 private static final long serialVersionUID = 20140724L;
60
61
62 private final Time.Rel sampleInterval;
63
64
65
66
67 public final Time.Rel getSampleInterval()
68 {
69 return this.sampleInterval;
70 }
71
72
73 private final ArrayList<Lane> path;
74
75
76 private final LengthVector.Rel cumulativeLengths;
77
78
79
80
81
82
83 public final Length.Rel getCumulativeLength(final int index)
84 {
85 int useIndex = -1 == index ? this.cumulativeLengths.size() - 1 : index;
86 try
87 {
88 return new Length.Rel(this.cumulativeLengths.get(useIndex));
89 }
90 catch (ValueException exception)
91 {
92 exception.printStackTrace();
93 }
94 return null;
95 }
96
97
98 private Time.Abs maximumTime = new Time.Abs(300, TimeUnit.SECOND);
99
100
101
102
103 public final Time.Abs getMaximumTime()
104 {
105 return this.maximumTime;
106 }
107
108
109
110
111 public final void setMaximumTime(final Time.Abs maximumTime)
112 {
113 this.maximumTime = maximumTime;
114 }
115
116
117 private transient EventListenerList listenerList = new EventListenerList();
118
119
120 private DatasetGroup datasetGroup = null;
121
122
123 private final String caption;
124
125
126
127
128
129
130
131 public TrajectoryPlot(final String caption, final Time.Rel sampleInterval, final List<Lane> path)
132 {
133 this.sampleInterval = sampleInterval;
134 this.path = new ArrayList<Lane>(path);
135 double[] endLengths = new double[path.size()];
136 double cumulativeLength = 0;
137 LengthVector.Rel lengths = null;
138 for (int i = 0; i < path.size(); i++)
139 {
140 Lane lane = path.get(i);
141 lane.addSampler(this);
142 cumulativeLength += lane.getLength().getSI();
143 endLengths[i] = cumulativeLength;
144 }
145 try
146 {
147 lengths = new LengthVector.Rel(endLengths, LengthUnit.SI, StorageType.DENSE);
148 }
149 catch (ValueException exception)
150 {
151 exception.printStackTrace();
152 }
153 this.cumulativeLengths = lengths;
154 this.caption = caption;
155 createChart(this);
156 this.reGraph();
157 }
158
159
160
161
162
163
164 private JFreeChart createChart(final JFrame container)
165 {
166 final JLabel statusLabel = new JLabel(" ", SwingConstants.CENTER);
167 container.add(statusLabel, BorderLayout.SOUTH);
168 ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
169 final JFreeChart result =
170 ChartFactory.createXYLineChart(this.caption, "", "", this, PlotOrientation.VERTICAL, false, false, false);
171
172 result.getPlot().setBackgroundPaint(new Color(0.9f, 0.9f, 0.9f));
173 FixCaption.fixCaption(result);
174 NumberAxis xAxis = new NumberAxis("\u2192 " + "time [s]");
175 xAxis.setLowerMargin(0.0);
176 xAxis.setUpperMargin(0.0);
177 NumberAxis yAxis = new NumberAxis("\u2192 " + "Distance [m]");
178 yAxis.setAutoRangeIncludesZero(false);
179 yAxis.setLowerMargin(0.0);
180 yAxis.setUpperMargin(0.0);
181 yAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
182 result.getXYPlot().setDomainAxis(xAxis);
183 result.getXYPlot().setRangeAxis(yAxis);
184 Length.Rel minimumPosition = Length.Rel.ZERO;
185 Length.Rel maximumPosition = getCumulativeLength(-1);
186 configureAxis(result.getXYPlot().getRangeAxis(), DoubleScalar.minus(maximumPosition, minimumPosition).getSI());
187 final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) result.getXYPlot().getRenderer();
188 renderer.setBaseLinesVisible(true);
189 renderer.setBaseShapesVisible(false);
190 renderer.setBaseShape(new Line2D.Float(0, 0, 0, 0));
191 final ChartPanel cp = new ChartPanel(result);
192 cp.setMouseWheelEnabled(true);
193 final PointerHandler ph = new PointerHandler()
194 {
195
196 @Override
197 void updateHint(final double domainValue, final double rangeValue)
198 {
199 if (Double.isNaN(domainValue))
200 {
201 statusLabel.setText(" ");
202 return;
203 }
204 String value = "";
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265 statusLabel.setText(String.format("t=%.0fs, distance=%.0fm%s", domainValue, rangeValue, value));
266 }
267 };
268 cp.addMouseMotionListener(ph);
269 cp.addMouseListener(ph);
270 container.add(cp, BorderLayout.CENTER);
271
272
273 JPopupMenu popupMenu = cp.getPopupMenu();
274 popupMenu.add(new JPopupMenu.Separator());
275 popupMenu.add(StandAloneChartWindow.createMenuItem(this));
276 return result;
277 }
278
279
280
281
282 public final void reGraph()
283 {
284 for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
285 {
286 if (dcl instanceof XYPlot)
287 {
288 configureAxis(((XYPlot) dcl).getDomainAxis(), this.maximumTime.getSI());
289 }
290 }
291 notifyListeners(new DatasetChangeEvent(this, null));
292 }
293
294
295
296
297
298 private void notifyListeners(final DatasetChangeEvent event)
299 {
300 for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
301 {
302 dcl.datasetChanged(event);
303 }
304 }
305
306
307
308
309
310
311 private static void configureAxis(final ValueAxis valueAxis, final double range)
312 {
313 valueAxis.setUpperBound(range);
314 valueAxis.setLowerMargin(0);
315 valueAxis.setUpperMargin(0);
316 valueAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
317 valueAxis.setAutoRange(true);
318 valueAxis.setAutoRangeMinimumSize(range);
319 valueAxis.centerRange(range / 2);
320 }
321
322
323 @Override
324 public void actionPerformed(final ActionEvent e)
325 {
326
327 }
328
329
330 private HashMap<String, Trajectory> trajectories = new HashMap<String, Trajectory>();
331
332
333 private ArrayList<Trajectory> trajectoryIndices = new ArrayList<Trajectory>();
334
335
336 public final void addData(final LaneBasedGTU car, final Lane lane) throws NetworkException, GTUException
337 {
338
339
340
341
342 double lengthOffset = 0;
343 int index = this.path.indexOf(lane);
344 if (index >= 0)
345 {
346 if (index > 0)
347 {
348 try
349 {
350 lengthOffset = this.cumulativeLengths.getSI(index - 1);
351 }
352 catch (ValueException exception)
353 {
354 exception.printStackTrace();
355 }
356 }
357 }
358 else
359 {
360 throw new Error("Car is not on any lane in the path");
361 }
362
363
364
365
366 String key = car.getId().toString();
367 Trajectory carTrajectory = this.trajectories.get(key);
368 if (null == carTrajectory)
369 {
370
371 carTrajectory = new Trajectory(key);
372 this.trajectoryIndices.add(carTrajectory);
373 this.trajectories.put(key, carTrajectory);
374
375 }
376 carTrajectory.addSegment(car, lane, lengthOffset);
377 }
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407 class Trajectory
408 {
409
410 private Time.Abs currentEndTime;
411
412
413
414
415
416 public final Time.Abs getCurrentEndTime()
417 {
418 return this.currentEndTime;
419 }
420
421
422 private Length.Rel currentEndPosition;
423
424
425
426
427
428 public final Length.Rel getCurrentEndPosition()
429 {
430 return this.currentEndPosition;
431 }
432
433
434 private final Object id;
435
436
437
438
439
440 public final Object getId()
441 {
442 return this.id;
443 }
444
445
446 private ArrayList<Double> positions = new ArrayList<Double>();
447
448
449 private int firstSample;
450
451
452
453
454
455 public Trajectory(final Object id)
456 {
457 this.id = id;
458 }
459
460
461
462
463
464
465
466
467
468
469 public final void addSegment(final LaneBasedGTU car, final Lane lane, final double positionOffset)
470 throws NetworkException, GTUException
471 {
472
473
474
475
476
477 try
478 {
479 final int startSample =
480 (int) Math.ceil(car.getOperationalPlan().getStartTime().getSI() / getSampleInterval().getSI());
481 final int endSample =
482 (int) (Math.ceil(car.getOperationalPlan().getEndTime().getSI() / getSampleInterval().getSI()));
483 for (int sample = startSample; sample < endSample; sample++)
484 {
485 Time.Abs sampleTime = new Time.Abs(sample * getSampleInterval().getSI(), TimeUnit.SI);
486 Double position = car.position(lane, car.getReference(), sampleTime).getSI() + positionOffset;
487 if (this.positions.size() > 0 && null != this.currentEndPosition && position < this.currentEndPosition.getSI() - 0.001)
488 {
489 if (0 != positionOffset)
490 {
491
492 break;
493 }
494
495 position = null;
496 }
497 if (this.positions.size() == 0)
498 {
499 this.firstSample = sample;
500 }
501
502
503
504
505
506
507
508 while (sample - this.firstSample > this.positions.size())
509 {
510
511 this.positions.add(null);
512
513 }
514 if (null != position && this.positions.size() > sample - this.firstSample)
515 {
516
517 continue;
518 }
519 this.positions.add(position);
520 }
521 this.currentEndTime = car.getOperationalPlan().getEndTime();
522 this.currentEndPosition =
523 new Length.Rel(car.position(lane, car.getReference(), this.currentEndTime).getSI() + positionOffset,
524 LengthUnit.SI);
525 if (car.getOperationalPlan().getEndTime().gt(getMaximumTime()))
526 {
527 setMaximumTime(car.getOperationalPlan().getEndTime());
528 }
529 }
530 catch (Exception e)
531 {
532
533 System.err.println("Trajectoryplot caught unexpected Exception: " + e.getMessage());
534 e.printStackTrace();
535 }
536 }
537
538
539
540
541
542 public int size()
543 {
544 return this.positions.size();
545 }
546
547
548
549
550
551 public double getTime(final int item)
552 {
553 return (item + this.firstSample) * getSampleInterval().getSI();
554 }
555
556
557
558
559
560 public double getDistance(final int item)
561 {
562 Double distance = this.positions.get(item);
563 if (null == distance)
564 {
565 return Double.NaN;
566 }
567 return this.positions.get(item);
568 }
569 }
570
571
572 @Override
573 public final int getSeriesCount()
574 {
575 return this.trajectories.size();
576 }
577
578
579 @Override
580 public final Comparable<Integer> getSeriesKey(final int series)
581 {
582 return series;
583 }
584
585
586 @SuppressWarnings("rawtypes")
587 @Override
588 public final int indexOf(final Comparable seriesKey)
589 {
590 if (seriesKey instanceof Integer)
591 {
592 return (Integer) seriesKey;
593 }
594 return -1;
595 }
596
597
598 @Override
599 public final void addChangeListener(final DatasetChangeListener listener)
600 {
601 this.listenerList.add(DatasetChangeListener.class, listener);
602 }
603
604
605 @Override
606 public final void removeChangeListener(final DatasetChangeListener listener)
607 {
608 this.listenerList.remove(DatasetChangeListener.class, listener);
609 }
610
611
612 @Override
613 public final DatasetGroup getGroup()
614 {
615 return this.datasetGroup;
616 }
617
618
619 @Override
620 public final void setGroup(final DatasetGroup group)
621 {
622 this.datasetGroup = group;
623 }
624
625
626 @Override
627 public final DomainOrder getDomainOrder()
628 {
629 return DomainOrder.ASCENDING;
630 }
631
632
633 @Override
634 public final int getItemCount(final int series)
635 {
636 return this.trajectoryIndices.get(series).size();
637 }
638
639
640 @Override
641 public final Number getX(final int series, final int item)
642 {
643 double v = getXValue(series, item);
644 if (Double.isNaN(v))
645 {
646 return null;
647 }
648 return v;
649 }
650
651
652 @Override
653 public final double getXValue(final int series, final int item)
654 {
655 return this.trajectoryIndices.get(series).getTime(item);
656 }
657
658
659 @Override
660 public final Number getY(final int series, final int item)
661 {
662 double v = getYValue(series, item);
663 if (Double.isNaN(v))
664 {
665 return null;
666 }
667 return v;
668 }
669
670
671 @Override
672 public final double getYValue(final int series, final int item)
673 {
674 return this.trajectoryIndices.get(series).getDistance(item);
675 }
676
677
678 @Override
679 public final JFrame addViewer()
680 {
681 JFrame result = new JFrame(this.caption);
682 result.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
683 JFreeChart newChart = createChart(result);
684 newChart.setTitle((String) null);
685 addChangeListener(newChart.getPlot());
686 return result;
687 }
688
689 }