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