View Javadoc
1   package org.opentrafficsim.demo.fd;
2   
3   import java.awt.Color;
4   import java.awt.Component;
5   import java.awt.Container;
6   import java.awt.Dimension;
7   import java.awt.event.ActionEvent;
8   import java.awt.event.ActionListener;
9   import java.lang.reflect.Field;
10  import java.util.ArrayList;
11  import java.util.Dictionary;
12  import java.util.Hashtable;
13  import java.util.LinkedHashMap;
14  import java.util.LinkedHashSet;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Set;
18  
19  import javax.swing.Box;
20  import javax.swing.BoxLayout;
21  import javax.swing.JButton;
22  import javax.swing.JComboBox;
23  import javax.swing.JLabel;
24  import javax.swing.JPanel;
25  import javax.swing.JPopupMenu;
26  import javax.swing.JSlider;
27  import javax.swing.border.EmptyBorder;
28  import javax.swing.event.ChangeEvent;
29  import javax.swing.event.ChangeListener;
30  
31  import org.djunits.unit.FrequencyUnit;
32  import org.djunits.unit.SpeedUnit;
33  import org.djunits.value.vdouble.scalar.Acceleration;
34  import org.djunits.value.vdouble.scalar.Direction;
35  import org.djunits.value.vdouble.scalar.Duration;
36  import org.djunits.value.vdouble.scalar.Frequency;
37  import org.djunits.value.vdouble.scalar.Length;
38  import org.djunits.value.vdouble.scalar.Speed;
39  import org.djutils.cli.CliUtil;
40  import org.djutils.exceptions.Try;
41  import org.djutils.means.HarmonicMean;
42  import org.opentrafficsim.base.parameters.ParameterException;
43  import org.opentrafficsim.base.parameters.ParameterTypes;
44  import org.opentrafficsim.base.parameters.Parameters;
45  import org.opentrafficsim.core.distributions.Generator;
46  import org.opentrafficsim.core.distributions.ProbabilityException;
47  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
48  import org.opentrafficsim.core.geometry.OTSPoint3D;
49  import org.opentrafficsim.core.gtu.GTU;
50  import org.opentrafficsim.core.gtu.GTUDirectionality;
51  import org.opentrafficsim.core.gtu.GTUErrorHandler;
52  import org.opentrafficsim.core.gtu.GTUException;
53  import org.opentrafficsim.core.gtu.GTUType;
54  import org.opentrafficsim.core.gtu.GTUType.DEFAULTS;
55  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
56  import org.opentrafficsim.core.idgenerator.IdGenerator;
57  import org.opentrafficsim.core.network.DirectedLinkPosition;
58  import org.opentrafficsim.core.network.Link;
59  import org.opentrafficsim.core.network.LinkType;
60  import org.opentrafficsim.core.network.NetworkException;
61  import org.opentrafficsim.core.parameters.ParameterFactory;
62  import org.opentrafficsim.draw.graphs.FundamentalDiagram;
63  import org.opentrafficsim.draw.graphs.FundamentalDiagram.FdLine;
64  import org.opentrafficsim.draw.graphs.FundamentalDiagram.FdSource;
65  import org.opentrafficsim.draw.graphs.FundamentalDiagram.Quantity;
66  import org.opentrafficsim.draw.graphs.GraphCrossSection;
67  import org.opentrafficsim.draw.graphs.GraphPath;
68  import org.opentrafficsim.draw.graphs.TrajectoryPlot;
69  import org.opentrafficsim.draw.graphs.road.GraphLaneUtil;
70  import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
71  import org.opentrafficsim.road.gtu.generator.CFBARoomChecker;
72  import org.opentrafficsim.road.gtu.generator.GeneratorPositions;
73  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBias;
74  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
75  import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator;
76  import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator.RoomChecker;
77  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGTUCharacteristics;
78  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGTUCharacteristicsGenerator;
79  import org.opentrafficsim.road.gtu.lane.VehicleModel;
80  import org.opentrafficsim.road.gtu.lane.perception.PerceptionFactory;
81  import org.opentrafficsim.road.gtu.lane.perception.categories.DirectInfrastructurePerception;
82  import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
83  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlannerFactory;
84  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModelFactory;
85  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlus;
86  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusFactory;
87  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLMRSPerceptionFactory;
88  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRS;
89  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRSFactory;
90  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlannerFactory;
91  import org.opentrafficsim.road.network.OTSRoadNetwork;
92  import org.opentrafficsim.road.network.factory.LaneFactory;
93  import org.opentrafficsim.road.network.lane.CrossSectionLink;
94  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
95  import org.opentrafficsim.road.network.lane.Lane;
96  import org.opentrafficsim.road.network.lane.LaneDirection;
97  import org.opentrafficsim.road.network.lane.LaneType;
98  import org.opentrafficsim.road.network.lane.OTSRoadNode;
99  import org.opentrafficsim.road.network.lane.Stripe.Permeable;
100 import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
101 import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
102 import org.opentrafficsim.road.network.sampling.RoadSampler;
103 import org.opentrafficsim.swing.graphs.SwingFundamentalDiagram;
104 import org.opentrafficsim.swing.graphs.SwingTrajectoryPlot;
105 import org.opentrafficsim.swing.gui.OTSAnimationPanel;
106 import org.opentrafficsim.swing.gui.OTSAnimationPanel.DemoPanelPosition;
107 import org.opentrafficsim.swing.script.AbstractSimulationScript;
108 
109 import nl.tudelft.simulation.dsol.swing.gui.TablePanel;
110 import nl.tudelft.simulation.jstats.distributions.DistNormal;
111 import nl.tudelft.simulation.jstats.streams.StreamInterface;
112 
113 /**
114  * Demo showing what fundamental diagrams are. This demo is for education purposes.
115  * <p>
116  * Copyright (c) 2020-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
117  * BSD-style license. See <a href="https://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
118  * </p>
119  * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
120  * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
121  * @author <a href="https://www.transport.citg.tudelft.nl">Wouter Schakel</a>
122  */
123 public class FundamentalDiagramDemo extends AbstractSimulationScript
124 {
125 
126     /** */
127     private static final long serialVersionUID = 20200509L;
128 
129     /** Dynamic demand. */
130     private Frequency demand = new Frequency(3500.0, FrequencyUnit.PER_HOUR);
131 
132     /** Dynamic truck fraction. */
133     private double truckFraction = 0.05;
134 
135     /** Speed limit. */
136     private Speed speedLimit = new Speed(120.0, SpeedUnit.KM_PER_HOUR);
137 
138     /** Tmin. */
139     private Duration tMin = Duration.instantiateSI(0.56);
140 
141     /** Tmax. */
142     private Duration tMax = Duration.instantiateSI(1.2);
143 
144     /** Panel splitting controls from graphs. */
145     private JPanel splitPanel;
146 
147     /** Panel with graphs. */
148     private TablePanel graphPanel;
149 
150     /** Sampler. */
151     private RoadSampler sampler;
152 
153     /** Selected cross-section. */
154     private String absoluteCrossSection1 = "1.50";
155 
156     /** Second selected cross-section. */
157     private String absoluteCrossSection2 = "None";
158 
159     /** Third selected cross-section. */
160     private String absoluteCrossSection3 = "None";
161 
162     /** Fd line in graphs based on settings. */
163     @SuppressWarnings("synthetic-access")
164     private DynamicFdLine fdLine = new DynamicFdLine();
165 
166     /** Fundamental diagrams that are updated when a setting is changed. */
167     private Set<FundamentalDiagram> funamentalDiagrams = new LinkedHashSet<>();
168 
169     /** Sources by name for each cross-section. */
170     private Map<String, FdSource> fdSourceMap = new LinkedHashMap<>();
171 
172     /** Panel of trajectory graph. */
173     private Container trajectoryPanel;
174 
175     /**
176      * Constructor.
177      */
178     public FundamentalDiagramDemo()
179     {
180         super("FD Demo", "Fundamental diagram demo");
181     }
182 
183     /**
184      * Main program.
185      * @param args String[]; the command line arguments (not used)
186      */
187     public static void main(final String[] args)
188     {
189         FundamentalDiagramDemoramDemo.html#FundamentalDiagramDemo">FundamentalDiagramDemo demo = new FundamentalDiagramDemo();
190         try
191         {
192             CliUtil.changeOptionDefault(demo, "simulationTime", "360000s"); // 100h
193             CliUtil.execute(demo, args);
194             demo.start();
195         }
196         catch (Exception exception)
197         {
198             exception.printStackTrace();
199         }
200     }
201 
202     /** {@inheritDoc} */
203     @Override
204     protected OTSRoadNetwork setupSimulation(final OTSSimulatorInterface sim) throws Exception
205     {
206         // Network
207         OTSRoadNetwork network = new OTSRoadNetwork("FD demo network", true, sim);
208         GTUType car = network.getGtuType(DEFAULTS.CAR);
209         GTUType truck = network.getGtuType(DEFAULTS.TRUCK);
210 
211         OTSRoadNode nodeA = new OTSRoadNode(network, "Origin", new OTSPoint3D(0.0, 0.0), Direction.ZERO);
212         OTSRoadNode nodeB = new OTSRoadNode(network, "Lane-drop", new OTSPoint3D(1500.0, 0.0), Direction.ZERO);
213         OTSRoadNode nodeC = new OTSRoadNode(network, "Destination", new OTSPoint3D(2500.0, 0.0), Direction.ZERO);
214 
215         LinkType linkType = network.getLinkType(LinkType.DEFAULTS.FREEWAY);
216         LaneKeepingPolicy policy = LaneKeepingPolicy.KEEPRIGHT;
217         Length laneWidth = Length.instantiateSI(3.5);
218         LaneType laneType = network.getLaneType(LaneType.DEFAULTS.FREEWAY);
219         Speed speedLim = new Speed(120.0, SpeedUnit.KM_PER_HOUR);
220 
221         List<Lane> lanesAB = new LaneFactory(network, nodeA, nodeB, linkType, sim, policy).leftToRight(3.0, laneWidth, laneType,
222             speedLim).addLanes(Permeable.BOTH, Permeable.BOTH).getLanes();
223         List<Lane> lanesBC = new LaneFactory(network, nodeB, nodeC, linkType, sim, policy).leftToRight(2.0, laneWidth, laneType,
224             speedLim).addLanes(Permeable.BOTH).getLanes();
225 
226         // Generator
227         // inter-arrival time generator
228         StreamInterface stream = sim.getReplication().getStream("generation");
229         Generator<Duration> interarrivelTimeGenerator = new Generator<Duration>()
230         {
231             @Override
232             public Duration draw() throws ProbabilityException, ParameterException
233             {
234                 @SuppressWarnings("synthetic-access")
235                 double mean = 1.0 / FundamentalDiagramDemo.this.demand.si;
236                 return Duration.instantiateSI(-mean * Math.log(stream.nextDouble()));
237             }
238         };
239         // GTU characteristics generator
240         CarFollowingModelFactory<IDMPlus> carFollowingModelFactory = new IDMPlusFactory(stream);
241         PerceptionFactory perceptionFactory = new DefaultLMRSPerceptionFactory();
242         LaneBasedTacticalPlannerFactory<LMRS> tacticalPlannerFactory = new LMRSFactory(carFollowingModelFactory,
243             perceptionFactory);
244         DistNormal fSpeed = new DistNormal(stream, 123.7 / 120.0, 12.0 / 120.0);
245         ParameterFactory parametersFactory = new ParameterFactory()
246         {
247             @SuppressWarnings("synthetic-access")
248             @Override
249             public void setValues(final Parameters parameters, final GTUType gtuType) throws ParameterException
250             {
251                 if (gtuType.equals(truck))
252                 {
253                     parameters.setParameter(ParameterTypes.A, Acceleration.instantiateSI(0.4));
254                 }
255                 else
256                 {
257                     parameters.setParameter(ParameterTypes.A, Acceleration.instantiateSI(2.0));
258                 }
259                 parameters.setParameter(ParameterTypes.FSPEED, fSpeed.draw()); // also for trucks due to low speed limit option
260                 parameters.setParameter(ParameterTypes.TMIN, FundamentalDiagramDemo.this.tMin);
261                 parameters.setParameter(ParameterTypes.TMAX, FundamentalDiagramDemo.this.tMax);
262             }
263         };
264         LaneBasedStrategicalRoutePlannerFactory laneBasedStrategicalPlannerFactory =
265                 new LaneBasedStrategicalRoutePlannerFactory(tacticalPlannerFactory, parametersFactory);
266         LaneBasedGTUCharacteristicsGenerator laneBasedGTUCharacteristicsGenerator = new LaneBasedGTUCharacteristicsGenerator()
267         {
268             @Override
269             public LaneBasedGTUCharacteristics draw() throws ProbabilityException, ParameterException, GTUException
270             {
271                 @SuppressWarnings("synthetic-access")
272                 GTUType gtuType = stream.nextDouble() > FundamentalDiagramDemo.this.truckFraction ? car : truck;
273                 return new LaneBasedGTUCharacteristics(GTUType.defaultCharacteristics(gtuType, network, stream),
274                     laneBasedStrategicalPlannerFactory, null, nodeA, nodeC, VehicleModel.MINMAX);
275             }
276         };
277         // generator positions
278         Set<DirectedLanePosition> initialPosition = new LinkedHashSet<>();
279         for (Lane lane : lanesAB)
280         {
281             initialPosition.add(new DirectedLanePosition(lane, Length.ZERO, GTUDirectionality.DIR_PLUS));
282         }
283         LaneBiases biases = new LaneBiases();
284         biases.addBias(car, LaneBias.bySpeed(new Speed(130.0, SpeedUnit.KM_PER_HOUR), new Speed(70.0, SpeedUnit.KM_PER_HOUR)));
285         biases.addBias(truck, LaneBias.TRUCK_RIGHT);
286         GeneratorPositions generatorPositions = GeneratorPositions.create(initialPosition, stream, biases);
287         // room checker
288         RoomChecker roomChecker = new CFBARoomChecker();
289         // id generator
290         IdGenerator idGenerator = new IdGenerator("");
291         // generator
292         LaneBasedGTUGenerator generator = new LaneBasedGTUGenerator("generator", interarrivelTimeGenerator,
293             laneBasedGTUCharacteristicsGenerator, generatorPositions, network, sim, roomChecker, idGenerator);
294         generator.setErrorHandler(GTUErrorHandler.DELETE);
295         generator.setInstantaneousLaneChange(true);
296         generator.setNoLaneChangeDistance(Length.instantiateSI(100.0));
297 
298         // Sinks
299         for (Lane lane : lanesBC)
300         {
301             new SinkSensor(lane, lane.getLength(), GTUDirectionality.DIR_PLUS, sim);
302         }
303 
304         return network;
305     }
306 
307     /** {@inheritDoc} */
308     @Override
309     protected void setupDemo(final OTSAnimationPanel animationPanel, final OTSRoadNetwork net)
310     {
311         this.fdLine.update();
312 
313         // Demo panel
314         animationPanel.createDemoPanel(DemoPanelPosition.BOTTOM);
315         animationPanel.getDemoPanel().setPreferredSize(new Dimension(1000, 500));
316         this.splitPanel = new JPanel(); // controls vs. graphs
317         JPanel controlPanel = new JPanel();
318         this.splitPanel.setLayout(new BoxLayout(this.splitPanel, BoxLayout.X_AXIS));
319         this.splitPanel.add(controlPanel);
320         animationPanel.getDemoPanel().add(this.splitPanel);
321 
322         // Control panel
323         controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
324         controlPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
325         controlPanel.setPreferredSize(new Dimension(250, 500));
326         Dimension controlSize = new Dimension(250, 0);
327         int strutSize = 20;
328         // cross section dropdown
329         JLabel crossSectionLabel = new JLabel("<html>Cross-section location [km]</html>");
330         crossSectionLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
331         crossSectionLabel.setMinimumSize(controlSize);
332         controlPanel.add(crossSectionLabel);
333         List<String> list = new ArrayList<>();
334         for (int i = 250; i <= 2250; i += 250)
335         {
336             list.add(String.format("%.2f", i / 1000.0));
337         }
338         JComboBox<String> crossSectionMenu = new JComboBox<String>(list.toArray(new String[0]));
339         Dimension crossSectionMenuSize = new Dimension(250, 25);
340         crossSectionMenu.setMinimumSize(crossSectionMenuSize);
341         crossSectionMenu.setMaximumSize(crossSectionMenuSize);
342         crossSectionMenu.setSelectedIndex(5);
343         crossSectionMenu.addActionListener(new ActionListener()
344         {
345             @SuppressWarnings({"synthetic-access", "unchecked"})
346             @Override
347             public void actionPerformed(final ActionEvent e)
348             {
349                 FundamentalDiagramDemo.this.absoluteCrossSection1 = (String) ((JComboBox<String>) e.getSource())
350                     .getSelectedItem();
351                 createFundamentalDiagramsForCrossSections();
352             }
353         });
354         controlPanel.add(crossSectionMenu);
355         // 2nd drop down
356         list = new ArrayList<>();
357         list.add("None");
358         for (int i = 250; i <= 2250; i += 250)
359         {
360             list.add(String.format("%.2f", i / 1000.0));
361         }
362         crossSectionMenu = new JComboBox<String>(list.toArray(new String[0]));
363         crossSectionMenu.setMinimumSize(crossSectionMenuSize);
364         crossSectionMenu.setMaximumSize(crossSectionMenuSize);
365         crossSectionMenu.setSelectedIndex(0);
366         crossSectionMenu.addActionListener(new ActionListener()
367         {
368             @SuppressWarnings({"synthetic-access", "unchecked"})
369             @Override
370             public void actionPerformed(final ActionEvent e)
371             {
372                 FundamentalDiagramDemo.this.absoluteCrossSection2 = (String) ((JComboBox<String>) e.getSource())
373                     .getSelectedItem();
374                 createFundamentalDiagramsForCrossSections();
375             }
376         });
377         controlPanel.add(crossSectionMenu);
378         // 3rd drop down
379         crossSectionMenu = new JComboBox<String>(list.toArray(new String[0]));
380         crossSectionMenu.setMinimumSize(crossSectionMenuSize);
381         crossSectionMenu.setMaximumSize(crossSectionMenuSize);
382         crossSectionMenu.setSelectedIndex(0);
383         crossSectionMenu.addActionListener(new ActionListener()
384         {
385             @SuppressWarnings({"synthetic-access", "unchecked"})
386             @Override
387             public void actionPerformed(final ActionEvent e)
388             {
389                 FundamentalDiagramDemo.this.absoluteCrossSection3 = (String) ((JComboBox<String>) e.getSource())
390                     .getSelectedItem();
391                 createFundamentalDiagramsForCrossSections();
392             }
393         });
394         controlPanel.add(crossSectionMenu);
395         // spacer
396         controlPanel.add(Box.createVerticalStrut(strutSize));
397         // reset button
398         JButton reset = new JButton("Clear data & graphs");
399         reset.setAlignmentX(Component.CENTER_ALIGNMENT);
400         reset.addActionListener(new ActionListener()
401         {
402             @SuppressWarnings("synthetic-access")
403             @Override
404             public void actionPerformed(final ActionEvent e)
405             {
406                 clearDataAndGraphs();
407             }
408         });
409         controlPanel.add(reset);
410         // spacer
411         controlPanel.add(Box.createVerticalStrut(strutSize));
412         // demand
413         JLabel demandLabel = new JLabel("<html>Demand [veh/h]</html>");
414         demandLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
415         demandLabel.setPreferredSize(controlSize);
416         controlPanel.add(demandLabel);
417         JSlider demandSlider = new JSlider(500, 5000, 3500);
418         demandSlider.setPreferredSize(controlSize);
419         demandSlider.setSnapToTicks(true);
420         demandSlider.setMinorTickSpacing(250);
421         demandSlider.setMajorTickSpacing(1000);
422         demandSlider.setPaintTicks(true);
423         demandSlider.setPaintLabels(true);
424         demandSlider.setToolTipText("<html>Demand [veh/h]</html>");
425         demandSlider.addChangeListener(new ChangeListener()
426         {
427             @SuppressWarnings("synthetic-access")
428             @Override
429             public void stateChanged(final ChangeEvent e)
430             {
431                 double value = ((JSlider) e.getSource()).getValue();
432                 FundamentalDiagramDemo.this.demand = new Frequency(value, FrequencyUnit.PER_HOUR);
433             }
434         });
435         controlPanel.add(demandSlider);
436         // spacer
437         controlPanel.add(Box.createVerticalStrut(strutSize));
438         // truck percentage
439         JLabel truckLabel = new JLabel("<html>Truck percentage [%]</html>");
440         truckLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
441         truckLabel.setPreferredSize(controlSize);
442         controlPanel.add(truckLabel);
443         JSlider truckSlider = new JSlider(0, 30, 5);
444         truckSlider.setPreferredSize(controlSize);
445         truckSlider.setSnapToTicks(true);
446         truckSlider.setMinorTickSpacing(5);
447         truckSlider.setMajorTickSpacing(10);
448         truckSlider.setPaintTicks(true);
449         truckSlider.setPaintLabels(true);
450         truckSlider.setToolTipText("<html>Truck percentage [%]</html>");
451         truckSlider.addChangeListener(new ChangeListener()
452         {
453             @SuppressWarnings("synthetic-access")
454             @Override
455             public void stateChanged(final ChangeEvent e)
456             {
457                 double value = ((JSlider) e.getSource()).getValue() / 100.0;
458                 FundamentalDiagramDemo.this.truckFraction = value;
459                 FundamentalDiagramDemo.this.fdLine.update();
460                 notifyPlotsChanged();
461             }
462         });
463         controlPanel.add(truckSlider);
464         // spacer
465         controlPanel.add(Box.createVerticalStrut(strutSize));
466         // Tmax
467         JLabel tLabel = new JLabel("<html>Max. headway [s]</html>");
468         tLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
469         tLabel.setPreferredSize(controlSize);
470         controlPanel.add(tLabel);
471         JSlider tSlider = new JSlider(10, 20, 12);
472         Dictionary<Integer, JLabel> labels = new Hashtable<>();
473         for (int i = 10; i <= 20; i += 2)
474         {
475             labels.put(i, new JLabel(String.format("%.1f", i / 10.0)));
476         }
477         tSlider.setLabelTable(labels);
478         tSlider.setPreferredSize(controlSize);
479         tSlider.setSnapToTicks(true);
480         tSlider.setMinorTickSpacing(1);
481         tSlider.setMajorTickSpacing(2);
482         tSlider.setPaintTicks(true);
483         tSlider.setPaintLabels(true);
484         tSlider.setToolTipText("<html>Max. headway [s]</html>");
485         tSlider.addChangeListener(new ChangeListener()
486         {
487             @SuppressWarnings("synthetic-access")
488             @Override
489             public void stateChanged(final ChangeEvent e)
490             {
491                 double value = ((JSlider) e.getSource()).getValue() / 10.0;
492                 FundamentalDiagramDemo.this.tMin = Duration.instantiateSI((0.56 / 1.2) * value);
493                 FundamentalDiagramDemo.this.tMax = Duration.instantiateSI(value);
494                 FundamentalDiagramDemo.this.fdLine.update();
495                 notifyPlotsChanged();
496                 for (GTU gtu : getNetwork().getGTUs())
497                 {
498                     try
499                     {
500                         gtu.getParameters().setParameter(ParameterTypes.TMIN, FundamentalDiagramDemo.this.tMin);
501                         gtu.getParameters().setParameter(ParameterTypes.TMAX, FundamentalDiagramDemo.this.tMax);
502                     }
503                     catch (ParameterException exception)
504                     {
505                         System.err.println("Unable to set headway parameter.");
506                     }
507                 }
508             }
509         });
510         controlPanel.add(tSlider);
511         // spacer
512         controlPanel.add(Box.createVerticalStrut(strutSize));
513         // V max
514         JLabel vLabel = new JLabel("<html>Speed limit [km/h]</html>");
515         vLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
516         vLabel.setPreferredSize(controlSize);
517         controlPanel.add(vLabel);
518         JSlider vSlider = new JSlider(80, 130, 120);
519         vSlider.setPreferredSize(controlSize);
520         vSlider.setSnapToTicks(true);
521         vSlider.setMinorTickSpacing(10);
522         vSlider.setMajorTickSpacing(10);
523         vSlider.setPaintTicks(true);
524         vSlider.setPaintLabels(true);
525         vSlider.setToolTipText("<html>Speed limit [km/h]</html>");
526         vSlider.addChangeListener(new ChangeListener()
527         {
528             @SuppressWarnings("synthetic-access")
529             @Override
530             public void stateChanged(final ChangeEvent e)
531             {
532                 FundamentalDiagramDemo.this.speedLimit = new Speed(((JSlider) e.getSource()).getValue(), SpeedUnit.KM_PER_HOUR);
533                 FundamentalDiagramDemo.this.fdLine.update();
534                 notifyPlotsChanged();
535                 for (Link link : getNetwork().getLinkMap().values())
536                 {
537                     for (Lane lane : ((CrossSectionLink) link).getLanes())
538                     {
539                         lane.setSpeedLimit(getNetwork().getGtuType(DEFAULTS.VEHICLE), FundamentalDiagramDemo.this.speedLimit);
540                     }
541                 }
542                 for (GTU gtu : getNetwork().getGTUs())
543                 {
544                     DirectInfrastructurePerception infra;
545                     try
546                     {
547                         infra = (DirectInfrastructurePerception) gtu.getTacticalPlanner().getPerception().getPerceptionCategory(
548                             InfrastructurePerception.class);
549                         // hack to reset the perceived speed limit cache
550                         Field field = DirectInfrastructurePerception.class.getDeclaredField("root");
551                         field.setAccessible(true);
552                         field.set(infra, null);
553                     }
554                     catch (OperationalPlanException | NoSuchFieldException | SecurityException | IllegalArgumentException
555                         | IllegalAccessException exception)
556                     {
557                         System.err.println("Unable to update perceived speed limit.");
558                     }
559                 }
560             }
561         });
562         controlPanel.add(vSlider);
563 
564         // Initiate graphs
565         clearDataAndGraphs();
566     }
567     
568     /**
569      * Response when settings were changed that affect the shape of the theoretical fundamental diagram, i.e. the FD line.
570      */
571     void notifyPlotsChanged()
572     {
573         for (FundamentalDiagram diagram : this.funamentalDiagrams)
574         {
575             diagram.notifyPlotChange();
576         }
577     }
578 
579     /**
580      * Response to clear data button.
581      */
582     private void clearDataAndGraphs()
583     {
584         // creating a sampler (and graphs) while running gives issues with scheduling in the past (sometimes)
585         boolean wasRunning = getSimulator().isStartingOrRunning();
586         if (wasRunning)
587         {
588             getSimulator().stop();
589         }
590 
591         // new sampler to loose all data
592         this.sampler = new RoadSampler(getNetwork());
593 
594         // create fundamental diagram source for each cross section (plots are (re)created in setCrossSections())
595         for (int i = 250; i <= 2250; i += 250)
596         {
597             List<String> names = new ArrayList<>();
598             names.add("Left");
599             names.add("Right");
600             Length lanePosition;
601             String linkId;
602             if (i >= 1500.0)
603             {
604                 lanePosition = Length.instantiateSI(i - 1500.0);
605                 linkId = "Lane-dropDestination";
606             }
607             else
608             {
609                 names.add(1, "Middle");
610                 lanePosition = Length.instantiateSI(i);
611                 linkId = "OriginLane-drop";
612             }
613             DirectedLinkPosition linkPosition = new DirectedLinkPosition(getNetwork().getLink(linkId), lanePosition,
614                 GTUDirectionality.DIR_PLUS);
615             GraphCrossSection<KpiLaneDirection> crossSection;
616             try
617             {
618                 crossSection = GraphLaneUtil.createCrossSection(names, linkPosition);
619             }
620             catch (NetworkException exception)
621             {
622                 throw new RuntimeException("Unable to create cross section.", exception);
623             }
624             Duration aggregationTime = Duration.instantiateSI(30.0);
625             FdSource source = FundamentalDiagram.sourceFromSampler(this.sampler, crossSection, true, aggregationTime, false);
626             this.fdSourceMap.put(String.format("%.2f", i / 1000.0), source);
627         }
628 
629         // create sampler plot
630         List<String> names = new ArrayList<>();
631         names.add("Left lane");
632         names.add("Middle lane");
633         names.add("Right lane");
634         List<LaneDirection> firstLanes = new ArrayList<>();
635         for (Lane lane : ((CrossSectionLink) getNetwork().getLink("OriginLane-drop")).getLanes())
636         {
637             firstLanes.add(new LaneDirection(lane, GTUDirectionality.DIR_PLUS));
638         }
639         GraphPath<KpiLaneDirection> path = Try.assign(() -> GraphLaneUtil.createPath(names, firstLanes), "");
640         TrajectoryPlot trajectoryPlot = new TrajectoryPlot("Trajectories", Duration.instantiateSI(5.0), getSimulator(),
641             this.sampler.getSamplerData(), path);
642         trajectoryPlot.updateFixedDomainRange(true);
643         SwingTrajectoryPlot swingTrajectoryPlot = new SwingTrajectoryPlot(trajectoryPlot)
644         {
645             /** */
646             private static final long serialVersionUID = 20200516L;
647 
648             /** {@inheritDoc} */
649             @Override
650             protected void addPopUpMenuItems(final JPopupMenu popupMenu)
651             {
652                 // disable
653             }
654         };
655         this.trajectoryPanel = swingTrajectoryPlot.getContentPane();
656 
657         // reset simulator state
658         if (!getSimulator().isStartingOrRunning() && wasRunning)
659         {
660             getSimulator().start();
661         }
662 
663         // create fundamental diagrams
664         createFundamentalDiagramsForCrossSections();
665     }
666 
667     /**
668      * Creates the fundamental diagrams based on the selected cross-sections.
669      */
670     private void createFundamentalDiagramsForCrossSections()
671     {
672         // avoid update scheduling in the past as simulator is running during creation
673         boolean wasRunning = getSimulator().isStartingOrRunning();
674         if (wasRunning)
675         {
676             getSimulator().stop();
677         }
678 
679         // keep color of selected theme in new GUI elements
680         Color color = null;
681         if (this.graphPanel != null)
682         {
683             color = this.graphPanel.getBackground();
684             // remove previous graphs
685             this.splitPanel.remove(this.graphPanel);
686         }
687         // create new panel for graphs
688         this.graphPanel = new TablePanel(2, 2);
689         this.graphPanel.setBorder(new EmptyBorder(0, 0, 20, 0));
690         if (color != null)
691         {
692             this.graphPanel.setBackground(color);
693         }
694         this.splitPanel.add(this.graphPanel);
695 
696         // compose a combined source if required
697         FdSource source;
698         if (this.absoluteCrossSection2.equals("None") && this.absoluteCrossSection3.equals("None"))
699         {
700             source = this.fdSourceMap.get(this.absoluteCrossSection1);
701             source.clearFundamentalDiagrams();
702         }
703         else
704         {
705             Map<String, FdSource> sources = new LinkedHashMap<>();
706             sources.put(this.absoluteCrossSection1 + "km", this.fdSourceMap.get(this.absoluteCrossSection1));
707             if (!this.absoluteCrossSection2.equals("None"))
708             {
709                 sources.put(this.absoluteCrossSection2 + "km", this.fdSourceMap.get(this.absoluteCrossSection2));
710             }
711             if (!this.absoluteCrossSection3.equals("None"))
712             {
713                 sources.put(this.absoluteCrossSection3 + "km", this.fdSourceMap.get(this.absoluteCrossSection3));
714             }
715             for (FdSource subSource : sources.values())
716             {
717                 subSource.clearFundamentalDiagrams();
718             }
719             source = FundamentalDiagram.combinedSource(sources);
720         }
721 
722         // because "Aggregate" and "Theoretical" looks ugly in the legend, we set the actual location as legend label
723         source.setAggregateName(this.absoluteCrossSection1);
724         
725         // create the fundamental diagrams
726         FundamentalDiagram fdPlota = new FundamentalDiagram("Density-speed", Quantity.DENSITY, Quantity.SPEED, getSimulator(),
727             source, this.fdLine);
728         FundamentalDiagram fdPlotb = new FundamentalDiagram("Density-flow", Quantity.DENSITY, Quantity.FLOW, getSimulator(),
729             source, this.fdLine);
730         FundamentalDiagram fdPlotc = new FundamentalDiagram("Flow-speed", Quantity.FLOW, Quantity.SPEED, getSimulator(), source,
731             this.fdLine);
732         
733         // recalculate over past data
734         source.recalculate(getSimulator().getSimulatorTime());
735         
736         // store graphs so changes to setting may affect the graphs
737         this.funamentalDiagrams.clear();
738         this.funamentalDiagrams.add(fdPlota);
739         this.funamentalDiagrams.add(fdPlotb);
740         this.funamentalDiagrams.add(fdPlotc);
741 
742         // create swing plots and add them to the graph panel
743         Container fda = new SwingFundamentalDiagramNoControl(fdPlota).getContentPane();
744         Container fdb = new SwingFundamentalDiagramNoControl(fdPlotb).getContentPane();
745         Container fdc = new SwingFundamentalDiagramNoControl(fdPlotc).getContentPane();
746         Dimension preferredGraphSize = new Dimension(375, 230);
747         fda.setPreferredSize(preferredGraphSize);
748         fdb.setPreferredSize(preferredGraphSize);
749         fdc.setPreferredSize(preferredGraphSize);
750         this.graphPanel.setCell(fda, 0, 0);
751         this.graphPanel.setCell(fdb, 0, 1);
752         this.graphPanel.setCell(fdc, 1, 0);
753 
754         // also add the trajectory panel (which hasn't changed but should be moved to the new graphs panel)
755         this.trajectoryPanel.setPreferredSize(preferredGraphSize);
756         this.graphPanel.setCell(this.trajectoryPanel, 1, 1);
757 
758         // set theme color of fundamental diagrams too (the little bar below where the mouse-over info is shown is visible)
759         if (color != null)
760         {
761             fda.setBackground(color);
762             fdb.setBackground(color);
763             fdc.setBackground(color);
764             this.trajectoryPanel.setBackground(color);
765         }
766         
767         // reorganize panels
768         fda.getParent().getParent().validate();
769 
770         // reset simulator state
771         if (!getSimulator().isStartingOrRunning() && wasRunning)
772         {
773             getSimulator().start();
774         }
775     }
776 
777     /**
778      * Class to disable aggregation period and update frequency.
779      */
780     private class SwingFundamentalDiagramNoControl extends SwingFundamentalDiagram
781     {
782         /** */
783         private static final long serialVersionUID = 20200516L;
784 
785         /**
786          * @param plot FundamentalDiagram; fundamental diagram
787          */
788         SwingFundamentalDiagramNoControl(final FundamentalDiagram plot)
789         {
790             super(plot);
791         }
792 
793         /** {@inheritDoc} */
794         @Override
795         protected void addPopUpMenuItems(final JPopupMenu popupMenu)
796         {
797             // disable
798         }
799     }
800     
801     /**
802      * Fundamental diagram line class based on local settings.
803      */
804     private class DynamicFdLine implements FdLine
805     {
806         /** Map of points for each quantity. */
807         private Map<Quantity, double[]> map = new LinkedHashMap<>();
808 
809         /** {@inheritDoc} */
810         @Override
811         public double[] getValues(final Quantity quantity)
812         {
813             return this.map.get(quantity);
814         }
815 
816         /** {@inheritDoc} */
817         @Override
818         public String getName()
819         {
820             return "Theoretical";
821         }
822 
823         /**
824          * Recalculates the FD based on input parameters.
825          */
826         @SuppressWarnings("synthetic-access")
827         public void update()
828         {
829             // harmonic mean of desired speed of cars and trucks
830             HarmonicMean<Speed, Double> meanSpeed = new HarmonicMean<>();
831             Speed carSpeed = FundamentalDiagramDemo.this.speedLimit.times(123.7 / 120.0);
832             meanSpeed.add(carSpeed, 1.0 - FundamentalDiagramDemo.this.truckFraction);
833             Speed truckSpeed = Speed.min(carSpeed, new Speed(85.0, SpeedUnit.KM_PER_HOUR));
834             meanSpeed.add(truckSpeed, FundamentalDiagramDemo.this.truckFraction);
835 
836             // mean of lengths
837             double meanLength = 4.19 * (1.0 - FundamentalDiagramDemo.this.truckFraction) + 12.0
838                 * FundamentalDiagramDemo.this.truckFraction;
839 
840             // calculate triangular FD parameters
841             double vMax = meanSpeed.getMean();
842             double kCrit = 1000.0 / (vMax * FundamentalDiagramDemo.this.tMax.si + meanLength + 3.0);
843             vMax = vMax * 3.6;
844             double qMax = vMax * kCrit;
845             int kJam = (int) (1000.0 / (meanLength + 3.0));
846 
847             // initialize and fill arrays for each quantity
848             double[] k = new double[kJam * 10 + 1];
849             double[] q = new double[kJam * 10 + 1];
850             double[] v = new double[kJam * 10 + 1];
851             for (int kk = 0; kk <= kJam * 10; kk++)
852             {
853                 double kVal = kk / 10.0;
854                 k[kk] = kVal;
855                 if (kVal > kCrit)
856                 {
857                     // congestion branch
858                     q[kk] = qMax * (1.0 - (kVal - kCrit) / (kJam - kCrit));
859                     v[kk] = q[kk] / k[kk];
860                 }
861                 else
862                 {
863                     // free-flow branch
864                     v[kk] = vMax;
865                     q[kk] = k[kk] * v[kk];
866                 }
867             }
868 
869             // cache values
870             this.map.put(Quantity.DENSITY, k);
871             this.map.put(Quantity.FLOW, q);
872             this.map.put(Quantity.SPEED, v);
873         }
874     }
875 
876 }