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