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