1 package org.opentrafficsim.demo.lanechange;
2
3 import java.awt.BorderLayout;
4 import java.awt.Frame;
5 import java.awt.geom.Line2D;
6 import java.lang.reflect.InvocationTargetException;
7 import java.rmi.RemoteException;
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.LinkedHashMap;
11 import java.util.LinkedHashSet;
12 import java.util.Map;
13
14 import javax.naming.NamingException;
15 import javax.swing.JFrame;
16 import javax.swing.JPanel;
17 import javax.swing.SwingUtilities;
18 import javax.swing.event.EventListenerList;
19
20 import nl.tudelft.simulation.dsol.SimRuntimeException;
21 import nl.tudelft.simulation.dsol.gui.swing.TablePanel;
22 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
23
24 import org.djunits.unit.TimeUnit;
25 import org.djunits.value.vdouble.scalar.DoubleScalar;
26 import org.djunits.value.vdouble.scalar.DoubleScalar.Abs;
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.event.PlotChangeEvent;
33 import org.jfree.chart.plot.Plot;
34 import org.jfree.chart.plot.PlotOrientation;
35 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
36 import org.jfree.data.DomainOrder;
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.OTS_SCALAR;
41 import org.opentrafficsim.core.dsol.OTSModelInterface;
42 import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
43 import org.opentrafficsim.core.geometry.OTSGeometryException;
44 import org.opentrafficsim.core.geometry.OTSPoint3D;
45 import org.opentrafficsim.core.gtu.GTUException;
46 import org.opentrafficsim.core.gtu.GTUType;
47 import org.opentrafficsim.core.network.NetworkException;
48 import org.opentrafficsim.core.network.OTSNode;
49 import org.opentrafficsim.core.network.route.CompleteRoute;
50 import org.opentrafficsim.road.car.LaneBasedIndividualCar;
51 import org.opentrafficsim.road.gtu.following.GTUFollowingModel;
52 import org.opentrafficsim.road.gtu.following.HeadwayGTU;
53 import org.opentrafficsim.road.gtu.following.IDM;
54 import org.opentrafficsim.road.gtu.following.IDMPlus;
55 import org.opentrafficsim.road.gtu.lane.changing.Altruistic;
56 import org.opentrafficsim.road.gtu.lane.changing.Egoistic;
57 import org.opentrafficsim.road.gtu.lane.changing.LaneChangeModel;
58 import org.opentrafficsim.road.gtu.lane.changing.LaneMovementStep;
59 import org.opentrafficsim.road.network.factory.LaneFactory;
60 import org.opentrafficsim.road.network.lane.Lane;
61 import org.opentrafficsim.road.network.lane.LaneType;
62 import org.opentrafficsim.road.network.route.CompleteLaneBasedRouteNavigator;
63 import org.opentrafficsim.simulationengine.SimpleSimulator;
64
65
66
67
68
69
70
71
72
73
74
75 public class LaneChangeGraph extends JFrame implements OTSModelInterface, OTS_SCALAR
76 {
77
78 private static final long serialVersionUID = 20141118L;
79
80
81 static final double[] STANDARDSPEEDS = {30, 50, 80, 100, 120};
82
83
84 private GTUFollowingModel carFollowingModel;
85
86
87 private ChartPanel[][] charts;
88
89
90 private static final Length.Rel LOWERBOUND = new Length.Rel(-500, METER);
91
92
93 private static final Length.Rel MIDPOINT = new Length.Rel(0, METER);
94
95
96 private static final Length.Rel UPPERBOUND = new Length.Rel(500, METER);
97
98
99 private static LaneChangeGraph lcs;
100
101
102
103
104
105
106 LaneChangeGraph(final String title, final JPanel mainPanel)
107 {
108 super(title);
109 setContentPane(mainPanel);
110 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
111 this.charts = new ChartPanel[2][STANDARDSPEEDS.length];
112 }
113
114
115
116
117
118
119
120
121
122
123 public static void main(final String[] args) throws NamingException, NetworkException,
124 SimRuntimeException, GTUException, OTSGeometryException
125 {
126 try
127 {
128 SwingUtilities.invokeAndWait(new Runnable()
129 {
130
131 @Override
132 public void run()
133 {
134 try
135 {
136 buildGUI(args);
137 }
138 catch (NamingException | NetworkException | SimRuntimeException | GTUException exception)
139 {
140 exception.printStackTrace();
141 }
142 }
143
144 });
145 }
146 catch (InvocationTargetException | InterruptedException exception)
147 {
148 exception.printStackTrace();
149 }
150 for (int row = 0; row < lcs.charts.length; row++)
151 {
152 LaneChangeModel laneChangeModel = 0 == row ? new Egoistic() : new Altruistic();
153 for (int index = 0; index < STANDARDSPEEDS.length; index++)
154 {
155 Speed.Abs speed = new Speed.Abs(STANDARDSPEEDS[index], KM_PER_HOUR);
156
157 double startSpeedDifference = -30;
158 double endSpeedDifference = startSpeedDifference + 60;
159 ChartData data = (ChartData) lcs.charts[row][index].getChart().getXYPlot().getDataset();
160 int beginRightKey = data.addSeries("Begin of no lane change to right");
161 int endRightKey = data.addSeries("End of no lane change to right");
162 int beginLeftKey = data.addSeries("Begin of no lane change to left");
163 int endLeftKey = data.addSeries("End of no lane change to left");
164 for (double speedDifference = startSpeedDifference; speedDifference <= endSpeedDifference; speedDifference +=
165 1)
166 {
167 Length.Rel criticalHeadway =
168 lcs.findDecisionPoint(LaneChangeGraph.LOWERBOUND, MIDPOINT, speed, new Speed.Rel(speedDifference,
169 KM_PER_HOUR), laneChangeModel, true);
170 if (null != criticalHeadway)
171 {
172 data.addXYPair(beginRightKey, speedDifference, criticalHeadway.getInUnit(METER));
173 }
174 criticalHeadway =
175 lcs.findDecisionPoint(MIDPOINT, LaneChangeGraph.UPPERBOUND, speed, new Speed.Rel(speedDifference,
176 KM_PER_HOUR), laneChangeModel, true);
177 if (null != criticalHeadway)
178 {
179 data.addXYPair(endRightKey, speedDifference, criticalHeadway.getInUnit(METER));
180 }
181 criticalHeadway =
182 lcs.findDecisionPoint(LaneChangeGraph.LOWERBOUND, MIDPOINT, speed, new Speed.Rel(speedDifference,
183 KM_PER_HOUR), laneChangeModel, false);
184 if (null != criticalHeadway)
185 {
186 data.addXYPair(beginLeftKey, speedDifference, criticalHeadway.getInUnit(METER));
187 }
188 else
189 {
190 lcs.findDecisionPoint(LaneChangeGraph.LOWERBOUND, MIDPOINT, speed, new Speed.Rel(speedDifference,
191 KM_PER_HOUR), laneChangeModel, false);
192 }
193 criticalHeadway =
194 lcs.findDecisionPoint(MIDPOINT, LaneChangeGraph.UPPERBOUND, speed, new Speed.Rel(speedDifference,
195 KM_PER_HOUR), laneChangeModel, false);
196 if (null != criticalHeadway)
197 {
198 data.addXYPair(endLeftKey, speedDifference, criticalHeadway.getInUnit(METER));
199 }
200 Plot plot = lcs.charts[row][index].getChart().getPlot();
201 plot.notifyListeners(new PlotChangeEvent(plot));
202 }
203 }
204 }
205
206 }
207
208
209
210
211
212
213
214
215
216 public static void buildGUI(final String[] args) throws NamingException, NetworkException,
217 SimRuntimeException, GTUException
218 {
219 JPanel mainPanel = new JPanel(new BorderLayout());
220 lcs = new LaneChangeGraph("Lane change graphs", mainPanel);
221 TablePanel chartsPanel = new TablePanel(STANDARDSPEEDS.length, 2);
222 mainPanel.add(chartsPanel, BorderLayout.CENTER);
223 for (int index = 0; index < STANDARDSPEEDS.length; index++)
224 {
225 lcs.charts[0][index] =
226 new ChartPanel(lcs.createChart(String.format("Egoistic reference car at %.0fkm/h", STANDARDSPEEDS[index]),
227 STANDARDSPEEDS[index]));
228 chartsPanel.setCell(lcs.charts[0][index], index, 0);
229 }
230 for (int index = 0; index < STANDARDSPEEDS.length; index++)
231 {
232 lcs.charts[1][index] =
233 new ChartPanel(lcs.createChart(String.format("Altruistic reference car at %.0fkm/h", STANDARDSPEEDS[index]),
234 STANDARDSPEEDS[index]));
235 chartsPanel.setCell(lcs.charts[1][index], index, 1);
236 }
237 lcs.pack();
238 lcs.setExtendedState(Frame.MAXIMIZED_BOTH);
239 lcs.setVisible(true);
240 }
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 private Length.Rel findDecisionPoint(Length.Rel low, Length.Rel high, final Speed.Abs referenceSpeed,
258 final Speed.Rel speedDifference, final LaneChangeModel laneChangeModel, final boolean mergeRight)
259 throws NamingException, NetworkException, SimRuntimeException, GTUException, OTSGeometryException
260 {
261
262 GTUType gtuType = GTUType.makeGTUType("car");
263 LaneType laneType = new LaneType("CarLane");
264 laneType.addCompatibility(gtuType);
265 final Speed.Abs speedLimit = new Speed.Abs(120, KM_PER_HOUR);
266
267 Lane[] lanes =
268 LaneFactory.makeMultiLane("Road with two lanes", new OTSNode("From", new OTSPoint3D(LOWERBOUND.getSI(), 0, 0)),
269 new OTSNode("To", new OTSPoint3D(UPPERBOUND.getSI(), 0, 0)), null, 2, laneType, speedLimit, null);
270
271 Map<Lane, Length.Rel> initialLongitudinalPositions = new LinkedHashMap<Lane, Length.Rel>();
272 initialLongitudinalPositions.put(lanes[mergeRight ? 0 : 1], new Length.Rel(0, METER));
273
274
275 SimpleSimulator simpleSimulator =
276 new SimpleSimulator(new Time.Abs(0.0, SECOND), new Time.Rel(0.0, SECOND), new Time.Rel(3600.0, SECOND), this);
277 this.carFollowingModel =
278 new IDMPlus(new Acceleration.Abs(1, METER_PER_SECOND_2), new Acceleration.Abs(1.5, METER_PER_SECOND_2),
279 new Length.Rel(2, METER), new Time.Rel(1, SECOND), 1d);
280 this.carFollowingModel =
281 new IDM(new Acceleration.Abs(1, METER_PER_SECOND_2), new Acceleration.Abs(1.5, METER_PER_SECOND_2),
282 new Length.Rel(2, METER), new Time.Rel(1, SECOND), 1d);
283
284 LaneBasedIndividualCar referenceCar =
285 new LaneBasedIndividualCar("ReferenceCar", gtuType, this.carFollowingModel, laneChangeModel,
286 initialLongitudinalPositions, referenceSpeed, new Length.Rel(4, METER), new Length.Rel(2, METER),
287 new Speed.Abs(150, KM_PER_HOUR), new CompleteLaneBasedRouteNavigator(new CompleteRoute("")), simpleSimulator);
288 Collection<HeadwayGTU> sameLaneGTUs = new LinkedHashSet<HeadwayGTU>();
289 sameLaneGTUs.add(new HeadwayGTU(referenceCar, 0));
290
291
292 LaneMovementStep lowResult =
293 computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, low, lanes[1], speedDifference,
294 mergeRight);
295 LaneMovementStep highResult =
296 computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, high, lanes[1], speedDifference,
297 mergeRight);
298 Length.Rel mid = null;
299 if (lowResult.getLaneChange() != highResult.getLaneChange())
300 {
301
302 final double delta = 0.1;
303 final int stepsNeeded = (int) Math.ceil(Math.log(DoubleScalar.minus(high, low).getSI() / delta) / Math.log(2));
304 for (int step = 0; step < stepsNeeded; step++)
305 {
306 Length.Rel mutableMid = low.plus(high).divideBy(2);
307 mid = mutableMid;
308 LaneMovementStep midResult =
309 computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, mid, lanes[1],
310 speedDifference, mergeRight);
311
312 if (midResult.getLaneChange() != lowResult.getLaneChange())
313 {
314 high = mid;
315 highResult = midResult;
316 }
317 else
318 {
319 low = mid;
320 lowResult = midResult;
321 }
322 }
323 }
324 else
325 {
326
327 computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, low, lanes[1], speedDifference,
328 mergeRight);
329 }
330 return mid;
331 }
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348 private LaneMovementStep computeLaneChange(final LaneBasedIndividualCar referenceCar,
349 final Collection<HeadwayGTU> sameLaneGTUs, final Speed.Abs speedLimit, final LaneChangeModel laneChangeModel,
350 final Length.Rel otherCarPosition, final Lane otherCarLane, final Speed.Rel deltaV, final boolean mergeRight)
351 throws NamingException, NetworkException, SimRuntimeException, GTUException
352 {
353 Map<Lane, Length.Rel> initialLongitudinalPositions = new LinkedHashMap<Lane, Length.Rel>();
354 initialLongitudinalPositions.put(otherCarLane, otherCarPosition);
355 LaneBasedIndividualCar otherCar =
356 new LaneBasedIndividualCar("otherCar", referenceCar.getGTUType(), this.carFollowingModel, laneChangeModel,
357 initialLongitudinalPositions, referenceCar.getLongitudinalVelocity().plus(deltaV), new Length.Rel(4, METER),
358 new Length.Rel(2, METER), new Speed.Abs(150, KM_PER_HOUR), new CompleteLaneBasedRouteNavigator(
359 new CompleteRoute("")), referenceCar.getSimulator());
360 Collection<HeadwayGTU> preferredLaneGTUs = new LinkedHashSet<HeadwayGTU>();
361 Collection<HeadwayGTU> nonPreferredLaneGTUs = new LinkedHashSet<HeadwayGTU>();
362 Length.Rel referenceCarPosition =
363 referenceCar.position(referenceCar.positions(referenceCar.getReference()).keySet().iterator().next(),
364 referenceCar.getReference());
365 HeadwayGTU otherGTU = new HeadwayGTU(otherCar, DoubleScalar.minus(otherCarPosition, referenceCarPosition).getSI());
366 if (mergeRight)
367 {
368 preferredLaneGTUs.add(otherGTU);
369 }
370 else
371 {
372 sameLaneGTUs.add(otherGTU);
373 }
374
375
376 LaneMovementStep result =
377 laneChangeModel.computeLaneChangeAndAcceleration(referenceCar, sameLaneGTUs, mergeRight ? preferredLaneGTUs
378 : null, mergeRight ? null : nonPreferredLaneGTUs, speedLimit, new Acceleration.Rel(0.3, METER_PER_SECOND_2),
379 new Acceleration.Rel(0.1, METER_PER_SECOND_2), new Acceleration.Rel(-0.3, METER_PER_SECOND_2));
380
381 sameLaneGTUs.remove(otherGTU);
382 otherCar.destroy();
383 return result;
384 }
385
386
387
388
389
390
391 private JFreeChart createChart(final String caption, final double speed)
392 {
393 ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
394 ChartData chartData = new ChartData();
395 JFreeChart chartPanel =
396 ChartFactory.createXYLineChart(caption, "", "", chartData, PlotOrientation.VERTICAL, false, false, false);
397 NumberAxis xAxis = new NumberAxis("\u2192 " + "\u0394v (other car speed minus reference car speed) [km/h]");
398 xAxis.setAutoRangeIncludesZero(true);
399 double minimumDifference = -30;
400 xAxis.setRange(minimumDifference, minimumDifference + 60);
401 xAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
402 NumberAxis yAxis = new NumberAxis("\u2192 " + "gross headway (\u0394s) [m]");
403 yAxis.setAutoRangeIncludesZero(true);
404 yAxis.setRange(LOWERBOUND.getSI(), UPPERBOUND.getSI());
405 yAxis.setInverted(true);
406 chartPanel.getXYPlot().setDomainAxis(xAxis);
407 chartPanel.getXYPlot().setRangeAxis(yAxis);
408 final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) chartPanel.getXYPlot().getRenderer();
409 renderer.setBaseLinesVisible(true);
410 renderer.setBaseShapesVisible(false);
411 renderer.setBaseShape(new Line2D.Float(0, 0, 0, 0));
412 return chartPanel;
413 }
414
415
416 @Override
417 public void constructModel(
418 final SimulatorInterface<Abs<TimeUnit>, DoubleScalar.Rel<TimeUnit>, OTSSimTimeDouble> simulator)
419 throws SimRuntimeException, RemoteException
420 {
421
422 }
423
424
425 @Override
426 public final SimulatorInterface<Abs<TimeUnit>, DoubleScalar.Rel<TimeUnit>, OTSSimTimeDouble> getSimulator()
427 throws RemoteException
428 {
429 return null;
430 }
431
432 }
433
434
435 class ChartData implements XYDataset
436 {
437
438
439 private ArrayList<ArrayList<Double>> xValues = new ArrayList<ArrayList<Double>>();
440
441
442 private ArrayList<ArrayList<Double>> yValues = new ArrayList<ArrayList<Double>>();
443
444
445 private ArrayList<String> seriesKeys = new ArrayList<String>();
446
447
448 private transient EventListenerList listenerList = new EventListenerList();
449
450
451 private DatasetGroup datasetGroup = null;
452
453
454
455
456
457
458 public final int addSeries(final String seriesName)
459 {
460 this.xValues.add(new ArrayList<Double>());
461 this.yValues.add(new ArrayList<Double>());
462 this.seriesKeys.add(seriesName);
463 return this.xValues.size() - 1;
464 }
465
466
467
468
469
470
471
472 public final void addXYPair(final int seriesKey, final double x, final double y)
473 {
474 this.xValues.get(seriesKey).add(x);
475 this.yValues.get(seriesKey).add(y);
476 }
477
478
479 @Override
480 public final int getSeriesCount()
481 {
482 return this.seriesKeys.size();
483 }
484
485
486 @Override
487 public final Comparable<?> getSeriesKey(final int series)
488 {
489 return this.seriesKeys.get(series);
490 }
491
492
493 @Override
494 public final int indexOf(@SuppressWarnings("rawtypes") final Comparable seriesKey)
495 {
496 return this.seriesKeys.indexOf(seriesKey);
497 }
498
499
500 @Override
501 public final void addChangeListener(final DatasetChangeListener listener)
502 {
503 this.listenerList.add(DatasetChangeListener.class, listener);
504 }
505
506
507 @Override
508 public final void removeChangeListener(final DatasetChangeListener listener)
509 {
510 this.listenerList.remove(DatasetChangeListener.class, listener);
511 }
512
513
514 @Override
515 public final DatasetGroup getGroup()
516 {
517 return this.datasetGroup;
518 }
519
520
521 @Override
522 public final void setGroup(final DatasetGroup group)
523 {
524 this.datasetGroup = group;
525 }
526
527
528 @Override
529 public final DomainOrder getDomainOrder()
530 {
531 return DomainOrder.ASCENDING;
532 }
533
534
535 @Override
536 public final int getItemCount(final int series)
537 {
538 return this.xValues.get(series).size();
539 }
540
541
542 @Override
543 public final Number getX(final int series, final int item)
544 {
545 return this.xValues.get(series).get(item);
546 }
547
548
549 @Override
550 public final double getXValue(final int series, final int item)
551 {
552 return this.xValues.get(series).get(item);
553 }
554
555
556 @Override
557 public final Number getY(final int series, final int item)
558 {
559 return this.yValues.get(series).get(item);
560 }
561
562
563 @Override
564 public final double getYValue(final int series, final int item)
565 {
566 return this.yValues.get(series).get(item);
567 }
568
569 }