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 }