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