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