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