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