View Javadoc
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   * Create a plot that characterizes a lane change graph.
62   * <p>
63   * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
64   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
65   * <p>
66   * @version 18 nov. 2014 <br>
67   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
68   */
69  public class LaneChangeGraph extends JFrame
70  {
71      /** */
72      private static final long serialVersionUID = 20141118L;
73  
74      /** Standard speed values in km/h. */
75      static double standardSpeeds[] = {30, 50, 80, 100, 120};
76  
77      /** The main window. */
78      LaneChangeGraph mainWindow;
79  
80      /** The car following model. */
81      GTUFollowingModel carFollowingModel;
82  
83      /** The graphs. */
84      ChartPanel[][] charts;
85  
86      /** Start of two lane road. */
87      static final DoubleScalar.Rel<LengthUnit> lowerBound = new DoubleScalar.Rel<LengthUnit>(-500, LengthUnit.METER);
88  
89      /** Position of reference vehicle on the two lane road. */
90      static final DoubleScalar.Rel<LengthUnit> midPoint = new DoubleScalar.Rel<LengthUnit>(0, LengthUnit.METER);
91  
92      /** End of two lane road. */
93      static final DoubleScalar.Rel<LengthUnit> upperBound = new DoubleScalar.Rel<LengthUnit>(500, LengthUnit.METER);
94  
95      /**
96       * Create a Lane Change Graph
97       * @param title String; title text of the window
98       * @param mainPanel JPanel; panel that will (indirectly?) contain the charts
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      * Then execution start point.
110      * @param args String[]; the command line arguments (not used)
111      * @throws NamingException
112      * @throws RemoteException
113      * @throws NetworkException
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                 // System.out.println("speed " + speed);
146                 double startSpeedDifference = -30;// standardSpeeds[index];
147                 double endSpeedDifference = startSpeedDifference + 60;// 150;
148                 ChartData data = (ChartData) lcs.charts[row][index].getChart().getXYPlot().getDataset();
149                 // int beginRightKey = data.addSeries("Begin of no lane change to right");
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                         // data.addXYPair(beginRightKey, speedDifference, criticalHeadway.getInUnit(LengthUnit.METER));
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      * Find the headway at which the decision to merge right changes.
198      * @param low minimum headway to consider
199      * @param high maximum headway to consider
200      * @param referenceSpeed DoubleScalar.Abs&lt;SpeedUnit&gt;; speed of the reference car
201      * @param speedDifference DoubleScalar.Rel&lt;SpeedUnit&gt;; speed of the other car minus speed of the reference car
202      * @param laneChangeModel LaneChangeModel; the lane change model to apply
203      * @param mergeRight boolean; if true; merge right is tested; if false; merge left is tested
204      * @return DoubleScalar.Rel&lt;LengthUnit&gt;
205      * @throws RemoteException
206      * @throws NamingException
207      * @throws NetworkException 
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         // Set up the network
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         // Create the reference vehicle
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         // The reference car only needs a fake simulator
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         // TODO play with the speed limit
247         // TODO play with the preferredLaneRouteIncentive
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             // Use bisection to home in onto the decision point
258             final double delta = 0.1; // [m]
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                 // System.out.println(String.format ("mid %.2fm: %s", mid.getSI(), midResult));
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             // System.out.println("Bisection failed");
284             computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, low, lanes[1], speedDifference,
285                 mergeRight);
286         }
287         return mid;
288     }
289 
290     /**
291      * @param referenceCar
292      * @param sameLaneGTUs
293      * @param speedLimit
294      * @param laneChangeModel
295      * @param otherCarPosition
296      * @param otherCarLane
297      * @param deltaV
298      * @param mergeRight
299      * @return
300      * @throws RemoteException
301      * @throws NamingException
302      * @throws SimRuntimeException 
303      * @throws NetworkException 
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         // System.out.println(referenceCar);
330         // System.out.println(otherCar);
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         // System.out.println(result);
338         sameLaneGTUs.remove(otherCar);
339         return result;
340     }
341 
342     /**
343      * @param caption String; the caption for the chart
344      * @param speed double; the speed of the reference vehicle
345      * @return
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     /** The X values. */
378     ArrayList<ArrayList<Double>> xValues = new ArrayList<ArrayList<Double>>();
379 
380     /** The Y values. */
381     ArrayList<ArrayList<Double>> yValues = new ArrayList<ArrayList<Double>>();
382 
383     /** The names of the series. */
384     ArrayList<String> seriesKeys = new ArrayList<String>();
385 
386     /** List of parties interested in changes of this ContourPlot. */
387     private transient EventListenerList listenerList = new EventListenerList();
388 
389     /** Not used internally. */
390     private DatasetGroup datasetGroup = null;
391 
392     /**
393      * Add storage for another series of XY values.
394      * @param seriesName String; the name of the new series
395      * @return int; the index to use to address the new series
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      * Add an XY pair to the data
407      * @param seriesKey int; key to the data series
408      * @param x double; x value of the pair
409      * @param y double; y value of the pair
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     /** {@inheritDoc} */
418     @Override
419     public int getSeriesCount()
420     {
421         return this.seriesKeys.size();
422     }
423 
424     /** {@inheritDoc} */
425     @Override
426     public Comparable<?> getSeriesKey(int series)
427     {
428         return this.seriesKeys.get(series);
429     }
430 
431     /** {@inheritDoc} */
432     @Override
433     public int indexOf(@SuppressWarnings("rawtypes") Comparable seriesKey)
434     {
435         return this.seriesKeys.indexOf(seriesKey);
436     }
437 
438     /** {@inheritDoc} */
439     @Override
440     public void addChangeListener(DatasetChangeListener listener)
441     {
442         this.listenerList.add(DatasetChangeListener.class, listener);
443     }
444 
445     /** {@inheritDoc} */
446     @Override
447     public void removeChangeListener(DatasetChangeListener listener)
448     {
449         this.listenerList.remove(DatasetChangeListener.class, listener);
450     }
451 
452     /** {@inheritDoc} */
453     @Override
454     public DatasetGroup getGroup()
455     {
456         return this.datasetGroup;
457     }
458 
459     /** {@inheritDoc} */
460     @Override
461     public void setGroup(DatasetGroup group)
462     {
463         this.datasetGroup = group;
464     }
465 
466     /** {@inheritDoc} */
467     @Override
468     public DomainOrder getDomainOrder()
469     {
470         return DomainOrder.ASCENDING;
471     }
472 
473     /** {@inheritDoc} */
474     @Override
475     public int getItemCount(int series)
476     {
477         return this.xValues.get(series).size();
478     }
479 
480     /** {@inheritDoc} */
481     @Override
482     public Number getX(int series, int item)
483     {
484         return this.xValues.get(series).get(item);
485     }
486 
487     /** {@inheritDoc} */
488     @Override
489     public double getXValue(int series, int item)
490     {
491         return this.xValues.get(series).get(item);
492     }
493 
494     /** {@inheritDoc} */
495     @Override
496     public Number getY(int series, int item)
497     {
498         return this.yValues.get(series).get(item);
499     }
500 
501     /** {@inheritDoc} */
502     @Override
503     public double getYValue(int series, int item)
504     {
505         return this.yValues.get(series).get(item);
506     }
507 
508 }