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