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