View Javadoc
1   package org.opentrafficsim.demo.lanechange;
2   
3   import java.awt.BasicStroke;
4   import java.awt.BorderLayout;
5   import java.awt.Color;
6   import java.rmi.RemoteException;
7   import java.util.ArrayList;
8   
9   import javax.naming.NamingException;
10  import javax.swing.JPanel;
11  import javax.swing.SwingUtilities;
12  import javax.swing.event.EventListenerList;
13  
14  import nl.tudelft.simulation.dsol.SimRuntimeException;
15  import nl.tudelft.simulation.dsol.gui.swing.TablePanel;
16  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
17  
18  import org.djunits.unit.TimeUnit;
19  import org.djunits.value.vdouble.scalar.DoubleScalar.Abs;
20  import org.djunits.value.vdouble.scalar.DoubleScalar.Rel;
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.plot.PlotOrientation;
27  import org.jfree.chart.plot.XYPlot;
28  import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
29  import org.jfree.data.DomainOrder;
30  import org.jfree.data.general.DatasetChangeEvent;
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.core.OTS_SCALAR;
35  import org.opentrafficsim.core.dsol.OTSModelInterface;
36  import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
37  import org.opentrafficsim.core.geometry.OTSGeometryException;
38  import org.opentrafficsim.core.geometry.OTSPoint3D;
39  import org.opentrafficsim.core.gtu.GTUType;
40  import org.opentrafficsim.core.network.NetworkException;
41  import org.opentrafficsim.core.network.OTSNode;
42  import org.opentrafficsim.core.network.route.CompleteRoute;
43  import org.opentrafficsim.gui.SimulatorFrame;
44  import org.opentrafficsim.road.network.factory.LaneFactory;
45  import org.opentrafficsim.road.network.lane.Lane;
46  import org.opentrafficsim.road.network.lane.LaneType;
47  import org.opentrafficsim.road.network.route.CompleteLaneBasedRouteNavigator;
48  import org.opentrafficsim.simulationengine.SimpleSimulator;
49  
50  /**
51   * <p>
52   * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
53   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
54   * <p>
55   * $LastChangedDate: 2015-09-24 19:14:49 +0200 (Thu, 24 Sep 2015) $, @version $Revision: 1430 $, by $Author: averbraeck $,
56   * initial version 15 apr. 2015 <br>
57   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
58   */
59  public class SuitabilityGraph implements OTSModelInterface, OTS_SCALAR
60  {
61      /** */
62      private static final long serialVersionUID = 20150415L;
63  
64      /** The JPanel that contains all the graphs. */
65      private JPanel graphPanel;
66  
67      /** Number of lanes on the main roadway (do not set higher than size of colorTable). */
68      private static final int LANECOUNT = 4;
69  
70      /** Speed limit values in km/h. */
71      private static final double[] SPEEDLIMITS = {30, 50, 80, 120};
72  
73      /** Arrangements of lanes to aim for. Negative numbers indicate lanes on right side of the roadway. */
74      private static final int[] TARGETLANES = {1, 2, -2, -1};
75  
76      /** Time horizon for lane changes. */
77      private Time.Rel timeHorizon = new Time.Rel(100, SECOND);
78  
79      /** Time range for graphs (also adjusts distance range). */
80      private Time.Rel timeRange = new Time.Rel(110, SECOND);
81  
82      /** Colors that correspond to the lanes; taken from electrical resistor color codes. */
83      private static final Color[] COLORTABLE = {new Color(160, 82, 45) /* brown */, Color.RED, Color.ORANGE,
84          Color.YELLOW, Color.GREEN, Color.BLUE, new Color(199, 21, 133) /* violet */, Color.GRAY, Color.WHITE};
85  
86      /** The graphs. */
87      private JFreeChart[][] charts;
88  
89      /**
90       * Start the program.
91       * @param args String[]; command line arguments (not used)
92       * @throws SimRuntimeException should never happen
93       */
94      public static void main(final String[] args) throws SimRuntimeException
95      {
96          SwingUtilities.invokeLater(new Runnable()
97          {
98              @Override
99              public void run()
100             {
101                 SuitabilityGraph suitabilityGraph = new SuitabilityGraph();
102                 new SimulatorFrame("Suitability graph", suitabilityGraph.getPanel());
103                 try
104                 {
105                     suitabilityGraph.drawPlots();
106                 }
107                 catch (NamingException | NetworkException | SimRuntimeException | OTSGeometryException exception)
108                 {
109                     exception.printStackTrace();
110                 }
111             }
112         });
113     }
114 
115     /**
116      * Draw the plots.
117      * @throws NetworkException on network inconsistency
118      * @throws NamingException on ???
119      * @throws SimRuntimeException on ???
120      * @throws OTSGeometryException
121      */
122     protected final void drawPlots() throws NamingException, NetworkException, SimRuntimeException,
123         OTSGeometryException
124     {
125         SimpleSimulator simulator =
126             new SimpleSimulator(new Time.Abs(0, TimeUnit.SI), new Time.Rel(0, TimeUnit.SI), new Time.Rel(99999,
127                 TimeUnit.SI), this);
128         final int rows = SPEEDLIMITS.length;
129         final int columns = TARGETLANES.length;
130         for (int row = 0; row < rows; row++)
131         {
132             int targetLaneConfiguration = TARGETLANES[row];
133             for (int column = 0; column < columns; column++)
134             {
135                 Speed.Abs speedLimit = new Speed.Abs(SPEEDLIMITS[column], KM_PER_HOUR);
136                 double mainLength = speedLimit.getSI() * this.timeRange.getSI();
137                 OTSNode from = new OTSNode("From", new OTSPoint3D(-mainLength, 0, 0));
138                 OTSNode branchPoint = new OTSNode("From", new OTSPoint3D(0, 0, 0));
139                 LaneType laneType = new LaneType("CarLane");
140                 GTUType gtuType = GTUType.makeGTUType("Car");
141                 laneType.addCompatibility(gtuType);
142                 Lane[] lanes =
143                     LaneFactory.makeMultiLane("Test road", from, branchPoint, null, LANECOUNT, laneType, speedLimit,
144                         simulator);
145                 OTSNode destination =
146                     new OTSNode("Destination", new OTSPoint3D(1000, targetLaneConfiguration > 0 ? 100 : -100, 0));
147                 LaneFactory.makeMultiLane("DestinationLink", branchPoint, destination, null, Math
148                     .abs(targetLaneConfiguration), targetLaneConfiguration > 0 ? 0 : LANECOUNT
149                     + targetLaneConfiguration, 0, laneType, speedLimit, simulator);
150                 OTSNode nonDestination =
151                     new OTSNode("Non-Destination", new OTSPoint3D(1000, targetLaneConfiguration > 0 ? -100 : 100, 0));
152                 LaneFactory.makeMultiLane("Non-DestinationLink", branchPoint, nonDestination, null, LANECOUNT
153                     - Math.abs(targetLaneConfiguration), targetLaneConfiguration > 0 ? LANECOUNT
154                     - targetLaneConfiguration : 0, 0, laneType, speedLimit, simulator);
155                 CompleteRoute route = new CompleteRoute("route");
156                 route.addNode(from);
157                 route.addNode(branchPoint);
158                 route.addNode(destination);
159                 CompleteLaneBasedRouteNavigator navigator = new CompleteLaneBasedRouteNavigator(route);
160                 SuitabilityData dataset =
161                     (SuitabilityData) ((XYPlot) (this.charts[row][column].getPlot())).getDataset();
162                 for (int laneIndex = 0; laneIndex < LANECOUNT; laneIndex++)
163                 {
164                     int key = dataset.addSeries("Lane " + (laneIndex + 1));
165                     Lane lane = lanes[laneIndex];
166                     for (int position = 0; position <= mainLength; position += 10)
167                     {
168                         Length.Rel longitudinalPosition = new Length.Rel(position, METER);
169                         Length.Rel suitability =
170                             navigator.suitability(lane, longitudinalPosition, gtuType, this.timeHorizon);
171                         if (suitability.getSI() <= mainLength)
172                         {
173                             dataset.addXYPair(key, mainLength - position, suitability.getSI());
174                         }
175                     }
176                     dataset.reGraph();
177                 }
178             }
179         }
180     }
181 
182     /**
183      * Instantiate the class.
184      */
185     public SuitabilityGraph()
186     {
187         this.graphPanel = new JPanel(new BorderLayout());
188         final int rows = SPEEDLIMITS.length;
189         final int columns = TARGETLANES.length;
190         TablePanel chartsPanel = new TablePanel(rows, rows);
191         this.graphPanel.add(chartsPanel, BorderLayout.CENTER);
192         this.charts = new JFreeChart[rows][columns];
193         for (int row = 0; row < rows; row++)
194         {
195             int targetLaneConfiguration = TARGETLANES[row];
196             String targetLaneDescription =
197                 String.format("%s lane %s exit", Math.abs(targetLaneConfiguration) == 1 ? "single" : "double",
198                     targetLaneConfiguration > 0 ? "left" : "right");
199             for (int column = 0; column < columns; column++)
200             {
201                 Speed.Abs speedLimit = new Speed.Abs(SPEEDLIMITS[column], KM_PER_HOUR);
202                 JFreeChart chart =
203                     createChart(String.format("Speed limit %.0f%s, %s", speedLimit.getInUnit(), speedLimit.getUnit(),
204                         targetLaneDescription), speedLimit);
205                 chartsPanel.setCell(new ChartPanel(chart), column, row);
206                 this.charts[row][column] = chart;
207             }
208         }
209     }
210 
211     /**
212      * @param caption String; the caption for the chart
213      * @param speedLimit DoubleScalar.Abs&lt;SpeedUnit&gt;; the speed limit
214      * @return JFreeChart; the newly created graph
215      */
216     private JFreeChart createChart(final String caption, final Speed.Abs speedLimit)
217     {
218         ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
219         XYDataset chartData = new SuitabilityData();
220         JFreeChart chartPanel =
221             ChartFactory.createXYLineChart(caption, "", "", chartData, PlotOrientation.VERTICAL, true, false, false);
222         chartPanel.setBorderVisible(true);
223         chartPanel.setBorderPaint(new Color(192, 192, 192));
224         NumberAxis timeAxis = new NumberAxis("\u2192 " + "Remaining time to junction [s]");
225         double distanceRange = this.timeRange.getSI() * speedLimit.getSI();
226         NumberAxis distanceAxis = new NumberAxis("\u2192 " + "Remaining distance to junction [m]");
227         distanceAxis.setRange(0, distanceRange);
228         distanceAxis.setInverted(true);
229         distanceAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
230         timeAxis.setAutoRangeIncludesZero(true);
231         timeAxis.setRange(0, this.timeRange.getSI());
232         timeAxis.setInverted(true);
233         timeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
234         // time axis gets messed up on auto range (probably due to all data being relative to distance axis)
235         // ((XYPlot) chartPanel.getPlot()).setDomainAxis(1, timeAxis);
236         NumberAxis yAxis = new NumberAxis("\u2192 " + "Distance to vacate lane [m]");
237         yAxis.setAutoRangeIncludesZero(true);
238         yAxis.setRange(-0.1, distanceRange);
239         chartPanel.getXYPlot().setDomainAxis(distanceAxis);
240         chartPanel.getXYPlot().setRangeAxis(yAxis);
241         final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) chartPanel.getXYPlot().getRenderer();
242         renderer.setBaseLinesVisible(true);
243         renderer.setBaseShapesVisible(false);
244         // Set paint color and stroke for each series
245         for (int index = 0; index < LANECOUNT; index++)
246         {
247             renderer.setSeriesPaint(index, COLORTABLE[index]);
248             renderer.setSeriesStroke(index, new BasicStroke(4.0f));
249         }
250 
251         return chartPanel;
252     }
253 
254     /**
255      * Return the JPanel that contains all the graphs.
256      * @return JPanel
257      */
258     public final JPanel getPanel()
259     {
260         return this.graphPanel;
261     }
262 
263     /** {@inheritDoc} */
264     @Override
265     public final void
266         constructModel(final SimulatorInterface<Abs<TimeUnit>, Rel<TimeUnit>, OTSSimTimeDouble> simulator)
267             throws SimRuntimeException, RemoteException
268     {
269         // Do nothing
270     }
271 
272     /** {@inheritDoc} */
273     @Override
274     public final SimulatorInterface<Abs<TimeUnit>, Rel<TimeUnit>, OTSSimTimeDouble> getSimulator()
275         throws RemoteException
276     {
277         return null;
278     }
279 
280 }
281 
282 /** */
283 class SuitabilityData implements XYDataset
284 {
285 
286     /** The X values. */
287     private ArrayList<ArrayList<Double>> xValues = new ArrayList<ArrayList<Double>>();
288 
289     /** The Y values. */
290     private ArrayList<ArrayList<Double>> yValues = new ArrayList<ArrayList<Double>>();
291 
292     /** The names of the series. */
293     private ArrayList<String> seriesKeys = new ArrayList<String>();
294 
295     /** List of parties interested in changes of this ContourPlot. */
296     private transient EventListenerList listenerList = new EventListenerList();
297 
298     /** Not used internally. */
299     private DatasetGroup datasetGroup = null;
300 
301     /**
302      * Redraw this graph (after the underlying data has been changed).
303      */
304     public final void reGraph()
305     {
306         notifyListeners(new DatasetChangeEvent(this, null)); // This guess work actually works!
307     }
308 
309     /**
310      * Notify interested parties of an event affecting this TrajectoryPlot.
311      * @param event DatasetChangedEvent
312      */
313     private void notifyListeners(final DatasetChangeEvent event)
314     {
315         for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
316         {
317             dcl.datasetChanged(event);
318         }
319     }
320 
321     /**
322      * Add storage for another series of XY values.
323      * @param seriesName String; the name of the new series
324      * @return int; the index to use to address the new series
325      */
326     public final int addSeries(final String seriesName)
327     {
328         this.xValues.add(new ArrayList<Double>());
329         this.yValues.add(new ArrayList<Double>());
330         this.seriesKeys.add(seriesName);
331         return this.xValues.size() - 1;
332     }
333 
334     /**
335      * Add an XY pair to the data.
336      * @param seriesKey int; key to the data series
337      * @param x double; x value of the pair
338      * @param y double; y value of the pair
339      */
340     public final void addXYPair(final int seriesKey, final double x, final double y)
341     {
342         this.xValues.get(seriesKey).add(x);
343         this.yValues.get(seriesKey).add(y);
344     }
345 
346     /** {@inheritDoc} */
347     @Override
348     public final int getSeriesCount()
349     {
350         return this.seriesKeys.size();
351     }
352 
353     /** {@inheritDoc} */
354     @Override
355     public final Comparable<?> getSeriesKey(final int series)
356     {
357         return this.seriesKeys.get(series);
358     }
359 
360     /** {@inheritDoc} */
361     @Override
362     public final int indexOf(@SuppressWarnings("rawtypes") final Comparable seriesKey)
363     {
364         return this.seriesKeys.indexOf(seriesKey);
365     }
366 
367     /** {@inheritDoc} */
368     @Override
369     public final void addChangeListener(final DatasetChangeListener listener)
370     {
371         this.listenerList.add(DatasetChangeListener.class, listener);
372     }
373 
374     /** {@inheritDoc} */
375     @Override
376     public final void removeChangeListener(final DatasetChangeListener listener)
377     {
378         this.listenerList.remove(DatasetChangeListener.class, listener);
379     }
380 
381     /** {@inheritDoc} */
382     @Override
383     public final DatasetGroup getGroup()
384     {
385         return this.datasetGroup;
386     }
387 
388     /** {@inheritDoc} */
389     @Override
390     public final void setGroup(final DatasetGroup group)
391     {
392         this.datasetGroup = group;
393     }
394 
395     /** {@inheritDoc} */
396     @Override
397     public DomainOrder getDomainOrder()
398     {
399         return DomainOrder.ASCENDING;
400     }
401 
402     /** {@inheritDoc} */
403     @Override
404     public final int getItemCount(final int series)
405     {
406         return this.xValues.get(series).size();
407     }
408 
409     /** {@inheritDoc} */
410     @Override
411     public final Number getX(final int series, final int item)
412     {
413         return this.xValues.get(series).get(item);
414     }
415 
416     /** {@inheritDoc} */
417     @Override
418     public final double getXValue(final int series, final int item)
419     {
420         return this.xValues.get(series).get(item);
421     }
422 
423     /** {@inheritDoc} */
424     @Override
425     public final Number getY(final int series, final int item)
426     {
427         return this.yValues.get(series).get(item);
428     }
429 
430     /** {@inheritDoc} */
431     @Override
432     public final double getYValue(final int series, final int item)
433     {
434         return this.yValues.get(series).get(item);
435     }
436 
437 }