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