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