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