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