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.data.util.GraphLaneUtil;
44 import org.opentrafficsim.animation.graphs.FdDataSource;
45 import org.opentrafficsim.animation.graphs.FundamentalDiagram;
46 import org.opentrafficsim.animation.graphs.GraphCrossSection;
47 import org.opentrafficsim.animation.graphs.GraphPath;
48 import org.opentrafficsim.animation.graphs.PlotScheduler;
49 import org.opentrafficsim.animation.graphs.TrajectoryPlot;
50 import org.opentrafficsim.animation.graphs.FundamentalDiagram.FdLine;
51 import org.opentrafficsim.animation.graphs.FundamentalDiagram.Quantity;
52 import org.opentrafficsim.base.OtsRuntimeException;
53 import org.opentrafficsim.base.logger.Logger;
54 import org.opentrafficsim.base.parameters.ParameterException;
55 import org.opentrafficsim.base.parameters.ParameterTypes;
56 import org.opentrafficsim.base.parameters.Parameters;
57 import org.opentrafficsim.core.definitions.Defaults;
58 import org.opentrafficsim.core.definitions.DefaultsNl;
59 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
60 import org.opentrafficsim.core.gtu.Gtu;
61 import org.opentrafficsim.core.gtu.GtuErrorHandler;
62 import org.opentrafficsim.core.gtu.GtuException;
63 import org.opentrafficsim.core.gtu.GtuType;
64 import org.opentrafficsim.core.idgenerator.IdSupplier;
65 import org.opentrafficsim.core.network.LinkPosition;
66 import org.opentrafficsim.core.network.LinkType;
67 import org.opentrafficsim.core.network.Network;
68 import org.opentrafficsim.core.network.NetworkException;
69 import org.opentrafficsim.core.network.Node;
70 import org.opentrafficsim.core.parameters.ParameterFactory;
71 import org.opentrafficsim.road.definitions.DefaultsRoadNl;
72 import org.opentrafficsim.road.gtu.LaneBookkeeping;
73 import org.opentrafficsim.road.gtu.VehicleModel;
74 import org.opentrafficsim.road.gtu.generator.CfBaRoomChecker;
75 import org.opentrafficsim.road.gtu.generator.GeneratorPositions;
76 import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBias;
77 import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
78 import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator;
79 import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator.RoomChecker;
80 import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristics;
81 import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristicsGenerator;
82 import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalRoutePlannerFactory;
83 import org.opentrafficsim.road.gtu.tactical.LaneBasedTacticalPlannerFactory;
84 import org.opentrafficsim.road.gtu.tactical.lmrs.Lmrs;
85 import org.opentrafficsim.road.gtu.tactical.lmrs.LmrsFactory;
86 import org.opentrafficsim.road.network.CrossSectionLink;
87 import org.opentrafficsim.road.network.Lane;
88 import org.opentrafficsim.road.network.LaneKeepingPolicy;
89 import org.opentrafficsim.road.network.LanePosition;
90 import org.opentrafficsim.road.network.LaneType;
91 import org.opentrafficsim.road.network.RoadNetwork;
92 import org.opentrafficsim.road.network.factory.LaneFactory;
93 import org.opentrafficsim.road.network.object.detector.SinkDetector;
94 import org.opentrafficsim.road.network.sampling.LaneDataRoad;
95 import org.opentrafficsim.road.network.sampling.RoadSampler;
96 import org.opentrafficsim.road.network.speed.LaneSpeedLimits;
97 import org.opentrafficsim.swing.graphs.OtsPlotScheduler;
98 import org.opentrafficsim.swing.graphs.SwingFundamentalDiagram;
99 import org.opentrafficsim.swing.graphs.SwingTrajectoryPlot;
100 import org.opentrafficsim.swing.gui.OtsSimulationPanel;
101 import org.opentrafficsim.swing.gui.OtsSimulationPanel.DemoPanelPosition;
102 import org.opentrafficsim.swing.gui.OtsSimulationPanelDecorator;
103 import org.opentrafficsim.swing.script.AbstractSimulationScript;
104
105 import nl.tudelft.simulation.dsol.swing.gui.TablePanel;
106 import nl.tudelft.simulation.jstats.distributions.DistNormal;
107 import nl.tudelft.simulation.jstats.streams.StreamInterface;
108
109
110
111
112
113
114
115
116
117
118
119 public class FundamentalDiagramDemo extends AbstractSimulationScript
120 {
121
122
123 private Frequency demand = new Frequency(3500.0, FrequencyUnit.PER_HOUR);
124
125
126 private double truckFraction = 0.05;
127
128
129 private LaneSpeedLimits speedLimits = new LaneSpeedLimits(new Speed(120.0, SpeedUnit.KM_PER_HOUR),
130 Map.of(DefaultsNl.TRUCK, new Speed(80.0, SpeedUnit.KM_PER_HOUR)));
131
132
133 private Duration tMin = Duration.ofSI(0.56);
134
135
136 private Duration tMax = Duration.ofSI(1.2);
137
138
139 private JPanel splitPanel;
140
141
142 private TablePanel graphPanel;
143
144
145 private RoadSampler sampler;
146
147
148 private String absoluteCrossSection1 = "1.50";
149
150
151 private String absoluteCrossSection2 = "None";
152
153
154 private String absoluteCrossSection3 = "None";
155
156
157 private DynamicFdLine fdLine = new DynamicFdLine();
158
159
160 private TrajectoryPlot trajectoryPlot;
161
162
163 private Set<FundamentalDiagram> funamentalDiagrams = new LinkedHashSet<>();
164
165
166 private Map<String, FdDataSource> fdSourceMap = new LinkedHashMap<>();
167
168
169 private Container trajectoryPanel;
170
171
172 private PlotScheduler scheduler;
173
174
175
176
177 public FundamentalDiagramDemo()
178 {
179 super("FD Demo", "Fundamental diagram demo");
180 }
181
182
183
184
185
186 public static void main(final String[] args)
187 {
188 FundamentalDiagramDemo demo = new FundamentalDiagramDemo();
189 try
190 {
191 CliUtil.changeOptionDefault(demo, "simulationTime", "360000s");
192 CliUtil.execute(demo, args);
193 demo.start();
194 }
195 catch (Exception exception)
196 {
197 exception.printStackTrace();
198 }
199 }
200
201 @Override
202 protected RoadNetwork setupSimulation(final OtsSimulatorInterface sim) throws Exception
203 {
204
205 RoadNetwork network = new RoadNetwork("FD demo network", sim);
206 GtuType car = DefaultsNl.CAR;
207 GtuType truck = DefaultsNl.TRUCK;
208
209 Node nodeA = new Node(network, "Origin", new Point2d(0.0, 0.0), Direction.ZERO);
210 Node nodeB = new Node(network, "Lane-drop", new Point2d(1500.0, 0.0), Direction.ZERO);
211 Node nodeC = new Node(network, "Destination", new Point2d(2500.0, 0.0), Direction.ZERO);
212
213 LinkType linkType = DefaultsNl.FREEWAY;
214 LaneKeepingPolicy policy = LaneKeepingPolicy.KEEPRIGHT;
215 Length laneWidth = Length.ofSI(3.5);
216 LaneType laneType = DefaultsRoadNl.FREEWAY;
217
218 List<Lane> lanesAB = new LaneFactory(network, nodeA, nodeB, linkType, sim, policy)
219 .leftToRight(3.0, laneWidth, laneType, this.speedLimits).addLanes(DefaultsRoadNl.DASHED, DefaultsRoadNl.DASHED)
220 .getLanes();
221 List<Lane> lanesBC = new LaneFactory(network, nodeB, nodeC, linkType, sim, policy)
222 .leftToRight(2.0, laneWidth, laneType, this.speedLimits).addLanes(DefaultsRoadNl.DASHED).getLanes();
223
224
225
226 StreamInterface stream = sim.getModel().getStream("generation");
227 Supplier<Duration> interarrivelTimeGenerator = new Supplier<Duration>()
228 {
229 @Override
230 public Duration get()
231 {
232 double mean = 1.0 / FundamentalDiagramDemo.this.demand.si;
233 return Duration.ofSI(-mean * Math.log(stream.nextDouble()));
234 }
235 };
236
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 DistNormal fSpeedGtu = new DistNormal(stream, 85.0 / 80.0, 2.5 / 80.0);
240 ParameterFactory parametersFactory = new ParameterFactory()
241 {
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());
254 parameters.setParameter(ParameterTypes.FSPEED_GTU, fSpeedGtu.draw());
255 parameters.setParameter(ParameterTypes.TMIN, FundamentalDiagramDemo.this.tMin);
256 parameters.setParameter(ParameterTypes.TMAX, FundamentalDiagramDemo.this.tMax);
257 }
258 };
259 LaneBasedStrategicalRoutePlannerFactory laneBasedStrategicalPlannerFactory =
260 new LaneBasedStrategicalRoutePlannerFactory(tacticalPlannerFactory, parametersFactory);
261 LaneBasedGtuCharacteristicsGenerator laneBasedGtuCharacteristicsGenerator = new LaneBasedGtuCharacteristicsGenerator()
262 {
263 @Override
264 public LaneBasedGtuCharacteristics draw() throws ParameterException, GtuException
265 {
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
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
285 RoomChecker roomChecker = new CfBaRoomChecker();
286
287 IdSupplier idGenerator = new IdSupplier("");
288
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
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 OtsSimulationPanelDecorator getDecorator()
306 {
307 return new OtsSimulationPanelDecorator()
308 {
309 @Override
310 public void setupDemo(final OtsSimulationPanel simulationPanel, final Network network)
311 {
312 FundamentalDiagramDemo.this.setupDemo(simulationPanel, network);
313 }
314 };
315 }
316
317
318
319
320
321
322 private void setupDemo(final OtsSimulationPanel animationPanel, final Network network)
323 {
324 this.fdLine.update();
325
326
327 animationPanel.createDemoPanel(DemoPanelPosition.BOTTOM);
328 animationPanel.getDemoPanel().setPreferredSize(new Dimension(1000, 500));
329 this.splitPanel = new JPanel();
330 JPanel controlPanel = new JPanel();
331 this.splitPanel.setLayout(new BoxLayout(this.splitPanel, BoxLayout.X_AXIS));
332 this.splitPanel.add(controlPanel);
333 animationPanel.getDemoPanel().add(this.splitPanel);
334
335
336 controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
337 controlPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
338 controlPanel.setPreferredSize(new Dimension(250, 500));
339 Dimension controlSize = new Dimension(250, 0);
340 int strutSize = 20;
341 setupDetectorSelectors(controlPanel, controlSize);
342
343 controlPanel.add(Box.createVerticalStrut(strutSize));
344
345 JButton reset = new JButton("Clear data & graphs");
346 reset.setAlignmentX(Component.CENTER_ALIGNMENT);
347 reset.addActionListener(new ActionListener()
348 {
349 @Override
350 public void actionPerformed(final ActionEvent e)
351 {
352 clearDataAndGraphs();
353 }
354 });
355 controlPanel.add(reset);
356
357 controlPanel.add(Box.createVerticalStrut(strutSize));
358
359 setupDemandSliders(controlPanel, controlSize, strutSize);
360
361 controlPanel.add(Box.createVerticalStrut(strutSize));
362 setupParameterSliders(controlPanel, controlSize, strutSize);
363
364
365 this.scheduler = new OtsPlotScheduler(getSimulator());
366 clearDataAndGraphs();
367 }
368
369
370
371
372
373
374 private void setupDetectorSelectors(final JPanel controlPanel, final Dimension controlSize)
375 {
376
377 JLabel crossSectionLabel = new JLabel("<html>Cross-section location [km]</html>");
378 crossSectionLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
379 crossSectionLabel.setMinimumSize(controlSize);
380 controlPanel.add(crossSectionLabel);
381 List<String> list = new ArrayList<>();
382 for (int i = 250; i <= 2250; i += 250)
383 {
384 list.add(String.format("%.2f", i / 1000.0));
385 }
386 JComboBox<String> crossSectionMenu = new JComboBox<String>(list.toArray(new String[0]));
387 Dimension crossSectionMenuSize = new Dimension(250, 25);
388 crossSectionMenu.setMinimumSize(crossSectionMenuSize);
389 crossSectionMenu.setMaximumSize(crossSectionMenuSize);
390 crossSectionMenu.setSelectedIndex(5);
391 crossSectionMenu.addActionListener(new ActionListener()
392 {
393 @SuppressWarnings({"unchecked"})
394 @Override
395 public void actionPerformed(final ActionEvent e)
396 {
397 FundamentalDiagramDemo.this.absoluteCrossSection1 =
398 ((String) ((JComboBox<String>) e.getSource()).getSelectedItem()).replace(",", ".");
399 createFundamentalDiagramsForCrossSections();
400 }
401 });
402 controlPanel.add(crossSectionMenu);
403
404 list = new ArrayList<>();
405 list.add("None");
406 for (int i = 250; i <= 2250; i += 250)
407 {
408 list.add(String.format("%.2f", i / 1000.0));
409 }
410 crossSectionMenu = new JComboBox<String>(list.toArray(new String[0]));
411 crossSectionMenu.setMinimumSize(crossSectionMenuSize);
412 crossSectionMenu.setMaximumSize(crossSectionMenuSize);
413 crossSectionMenu.setSelectedIndex(0);
414 crossSectionMenu.addActionListener(new ActionListener()
415 {
416 @SuppressWarnings({"unchecked"})
417 @Override
418 public void actionPerformed(final ActionEvent e)
419 {
420 FundamentalDiagramDemo.this.absoluteCrossSection2 =
421 ((String) ((JComboBox<String>) e.getSource()).getSelectedItem()).replace(",", ".");
422 createFundamentalDiagramsForCrossSections();
423 }
424 });
425 controlPanel.add(crossSectionMenu);
426
427 crossSectionMenu = new JComboBox<String>(list.toArray(new String[0]));
428 crossSectionMenu.setMinimumSize(crossSectionMenuSize);
429 crossSectionMenu.setMaximumSize(crossSectionMenuSize);
430 crossSectionMenu.setSelectedIndex(0);
431 crossSectionMenu.addActionListener(new ActionListener()
432 {
433 @SuppressWarnings({"unchecked"})
434 @Override
435 public void actionPerformed(final ActionEvent e)
436 {
437 FundamentalDiagramDemo.this.absoluteCrossSection3 =
438 ((String) ((JComboBox<String>) e.getSource()).getSelectedItem()).replace(",", ".");
439 createFundamentalDiagramsForCrossSections();
440 }
441 });
442 controlPanel.add(crossSectionMenu);
443 }
444
445
446
447
448
449
450
451 private void setupDemandSliders(final JPanel controlPanel, final Dimension controlSize, final int strutSize)
452 {
453 JLabel demandLabel = new JLabel("<html>Demand [veh/h]</html>");
454 demandLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
455 demandLabel.setPreferredSize(controlSize);
456 controlPanel.add(demandLabel);
457 JSlider demandSlider = new JSlider(500, 5000, 3500);
458 demandSlider.setPreferredSize(controlSize);
459 demandSlider.setSnapToTicks(true);
460 demandSlider.setMinorTickSpacing(250);
461 demandSlider.setMajorTickSpacing(1000);
462 demandSlider.setPaintTicks(true);
463 demandSlider.setPaintLabels(true);
464 demandSlider.setToolTipText("<html>Demand [veh/h]</html>");
465 demandSlider.addChangeListener(new ChangeListener()
466 {
467 @Override
468 public void stateChanged(final ChangeEvent e)
469 {
470 double value = ((JSlider) e.getSource()).getValue();
471 FundamentalDiagramDemo.this.demand = new Frequency(value, FrequencyUnit.PER_HOUR);
472 }
473 });
474 controlPanel.add(demandSlider);
475
476 controlPanel.add(Box.createVerticalStrut(strutSize));
477
478 JLabel truckLabel = new JLabel("<html>Truck percentage [%]</html>");
479 truckLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
480 truckLabel.setPreferredSize(controlSize);
481 controlPanel.add(truckLabel);
482 JSlider truckSlider = new JSlider(0, 30, 5);
483 truckSlider.setPreferredSize(controlSize);
484 truckSlider.setSnapToTicks(true);
485 truckSlider.setMinorTickSpacing(5);
486 truckSlider.setMajorTickSpacing(10);
487 truckSlider.setPaintTicks(true);
488 truckSlider.setPaintLabels(true);
489 truckSlider.setToolTipText("<html>Truck percentage [%]</html>");
490 truckSlider.addChangeListener(new ChangeListener()
491 {
492 @Override
493 public void stateChanged(final ChangeEvent e)
494 {
495 double value = ((JSlider) e.getSource()).getValue() / 100.0;
496 FundamentalDiagramDemo.this.truckFraction = value;
497 FundamentalDiagramDemo.this.fdLine.update();
498 notifyPlotsChanged();
499 }
500 });
501 controlPanel.add(truckSlider);
502 }
503
504
505
506
507
508
509
510 private void setupParameterSliders(final JPanel controlPanel, final Dimension controlSize, final int strutSize)
511 {
512
513 JLabel tLabel = new JLabel("<html>Max. headway [s]</html>");
514 tLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
515 tLabel.setPreferredSize(controlSize);
516 controlPanel.add(tLabel);
517 JSlider tSlider = new JSlider(10, 20, 12);
518 Dictionary<Integer, JLabel> labels = new Hashtable<>();
519 for (int i = 10; i <= 20; i += 2)
520 {
521 labels.put(i, new JLabel(String.format("%.1f", i / 10.0)));
522 }
523 tSlider.setLabelTable(labels);
524 tSlider.setPreferredSize(controlSize);
525 tSlider.setSnapToTicks(true);
526 tSlider.setMinorTickSpacing(1);
527 tSlider.setMajorTickSpacing(2);
528 tSlider.setPaintTicks(true);
529 tSlider.setPaintLabels(true);
530 tSlider.setToolTipText("<html>Max. headway [s]</html>");
531 tSlider.addChangeListener(new ChangeListener()
532 {
533 @Override
534 public void stateChanged(final ChangeEvent e)
535 {
536 double value = ((JSlider) e.getSource()).getValue() / 10.0;
537 FundamentalDiagramDemo.this.tMin = Duration.ofSI((0.56 / 1.2) * value);
538 FundamentalDiagramDemo.this.tMax = Duration.ofSI(value);
539 FundamentalDiagramDemo.this.fdLine.update();
540 notifyPlotsChanged();
541 for (Gtu gtu : getNetwork().getGTUs())
542 {
543 try
544 {
545 gtu.getParameters().setClaimedParameter(ParameterTypes.TMIN, FundamentalDiagramDemo.this.tMin, tSlider);
546 gtu.getParameters().setClaimedParameter(ParameterTypes.TMAX, FundamentalDiagramDemo.this.tMax, tSlider);
547 }
548 catch (ParameterException exception)
549 {
550
551 try
552 {
553 gtu.getParameters().setClaimedParameter(ParameterTypes.TMAX, FundamentalDiagramDemo.this.tMax,
554 tSlider);
555 gtu.getParameters().setClaimedParameter(ParameterTypes.TMIN, FundamentalDiagramDemo.this.tMin,
556 tSlider);
557 }
558 catch (ParameterException exception2)
559 {
560 Logger.ots().error("Unable to set headway parameter.");
561 }
562 }
563 }
564 }
565 });
566 controlPanel.add(tSlider);
567
568 controlPanel.add(Box.createVerticalStrut(strutSize));
569
570 JLabel vLabel = new JLabel("<html>Speed limit [km/h]</html>");
571 vLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
572 vLabel.setPreferredSize(controlSize);
573 controlPanel.add(vLabel);
574 JSlider vSlider = new JSlider(80, 130, 120);
575 vSlider.setPreferredSize(controlSize);
576 vSlider.setSnapToTicks(true);
577 vSlider.setMinorTickSpacing(10);
578 vSlider.setMajorTickSpacing(10);
579 vSlider.setPaintTicks(true);
580 vSlider.setPaintLabels(true);
581 vSlider.setToolTipText("<html>Speed limit [km/h]</html>");
582 vSlider.addChangeListener(new ChangeListener()
583 {
584 @Override
585 public void stateChanged(final ChangeEvent e)
586 {
587 FundamentalDiagramDemo.this.speedLimits
588 .addSpeedLimit(new Speed(((JSlider) e.getSource()).getValue(), SpeedUnit.KM_PER_HOUR));
589 FundamentalDiagramDemo.this.fdLine.update();
590 notifyPlotsChanged();
591 }
592 });
593 controlPanel.add(vSlider);
594 }
595
596
597
598
599 void notifyPlotsChanged()
600 {
601 for (FundamentalDiagram diagram : this.funamentalDiagrams)
602 {
603 diagram.notifyPlotChange();
604 }
605 }
606
607
608
609
610 private void clearDataAndGraphs()
611 {
612
613 boolean wasRunning = getSimulator().isStartingOrRunning();
614 if (wasRunning)
615 {
616 getSimulator().stop();
617 }
618
619
620 if (this.sampler != null)
621 {
622 this.sampler.getSamplerData().getLanes().forEach((l) -> this.sampler.finalizeRecording((LaneDataRoad) l));
623 }
624 this.sampler = new RoadSampler(getNetwork());
625
626
627 for (int i = 250; i <= 2250; i += 250)
628 {
629 List<String> names = new ArrayList<>();
630 names.add("Left");
631 names.add("Right");
632 Length lanePosition;
633 String linkId;
634 if (i >= 1500.0)
635 {
636 lanePosition = Length.ofSI(i - 1500.0);
637 linkId = "Lane-dropDestination";
638 }
639 else
640 {
641 names.add(1, "Middle");
642 lanePosition = Length.ofSI(i);
643 linkId = "OriginLane-drop";
644 }
645 LinkPosition linkPosition = new LinkPosition(getNetwork().getLink(linkId).get(), lanePosition);
646 GraphCrossSection<LaneDataRoad> crossSection;
647 try
648 {
649 crossSection = GraphLaneUtil.createCrossSection(names, linkPosition);
650 }
651 catch (NetworkException exception)
652 {
653 throw new OtsRuntimeException("Unable to create cross section.", exception);
654 }
655 Duration aggregationTime = Duration.ofSI(30.0);
656 FdDataSource source =
657 FdDataSource.sourceFromSampler(this.sampler, this.scheduler, crossSection, true, aggregationTime, false);
658 this.fdSourceMap.put(String.format("%.2f", i / 1000.0).replace(",", "."), source);
659 }
660
661
662 if (this.trajectoryPlot != null)
663 {
664 this.scheduler.cancelEvent(this.trajectoryPlot);
665 }
666 this.funamentalDiagrams.forEach((fd) -> this.scheduler.cancelEvent(fd));
667
668
669 List<String> names = new ArrayList<>();
670 names.add("Left lane");
671 names.add("Middle lane");
672 names.add("Right lane");
673 List<Lane> firstLanes = new ArrayList<>();
674 for (Lane lane : ((CrossSectionLink) getNetwork().getLink("OriginLane-drop").get()).getLanes())
675 {
676 firstLanes.add(lane);
677 }
678 GraphPath<LaneDataRoad> path = Try.assign(() -> GraphLaneUtil.createPath(names, firstLanes), "Missing speed limit");
679 this.trajectoryPlot =
680 new TrajectoryPlot("Trajectories", Duration.ofSI(5.0), this.scheduler, this.sampler.getSamplerData(), path);
681 this.trajectoryPlot.updateFixedDomainRange(true);
682 SwingTrajectoryPlot swingTrajectoryPlot = new SwingTrajectoryPlot(this.trajectoryPlot)
683 {
684
685 private static final long serialVersionUID = 20251121L;
686
687 @Override
688 protected void addPopUpMenuItems(final JPopupMenu popupMenu)
689 {
690
691 }
692 };
693 this.trajectoryPanel = swingTrajectoryPlot.getContentPane();
694
695
696 createFundamentalDiagramsForCrossSections();
697
698
699 if (!getSimulator().isStartingOrRunning() && wasRunning)
700 {
701 getSimulator().start();
702 }
703 }
704
705
706
707
708 private void createFundamentalDiagramsForCrossSections()
709 {
710
711 boolean wasRunning = getSimulator().isStartingOrRunning();
712 if (wasRunning)
713 {
714 getSimulator().stop();
715 }
716
717
718 Color color = null;
719 if (this.graphPanel != null)
720 {
721 color = this.graphPanel.getBackground();
722
723 this.splitPanel.remove(this.graphPanel);
724 }
725
726 this.graphPanel = new TablePanel(2, 2);
727 this.graphPanel.setBorder(new EmptyBorder(0, 0, 20, 0));
728 if (color != null)
729 {
730 this.graphPanel.setBackground(color);
731 }
732 this.splitPanel.add(this.graphPanel);
733
734
735 FdDataSource source;
736 if (this.absoluteCrossSection2.equals("None") && this.absoluteCrossSection3.equals("None"))
737 {
738 source = this.fdSourceMap.get(this.absoluteCrossSection1);
739 if (source == null)
740 {
741 source = this.fdSourceMap.get(this.absoluteCrossSection1);
742 }
743 source.clearPlots();
744 }
745 else
746 {
747 Map<String, FdDataSource> sources = new LinkedHashMap<>();
748 sources.put(this.absoluteCrossSection1 + "km", this.fdSourceMap.get(this.absoluteCrossSection1));
749 if (!this.absoluteCrossSection2.equals("None"))
750 {
751 sources.put(this.absoluteCrossSection2 + "km", this.fdSourceMap.get(this.absoluteCrossSection2));
752 }
753 if (!this.absoluteCrossSection3.equals("None"))
754 {
755 sources.put(this.absoluteCrossSection3 + "km", this.fdSourceMap.get(this.absoluteCrossSection3));
756 }
757 for (FdDataSource subSource : sources.values())
758 {
759 subSource.clearPlots();
760 }
761 source = FdDataSource.combinedSource(sources);
762 }
763
764
765 source.setAggregateName(this.absoluteCrossSection1);
766
767
768 FundamentalDiagram fdPlota =
769 new FundamentalDiagram("Density-speed", Quantity.DENSITY, Quantity.SPEED, source, this.fdLine);
770 FundamentalDiagram fdPlotb =
771 new FundamentalDiagram("Density-flow", Quantity.DENSITY, Quantity.FLOW, source, this.fdLine);
772 FundamentalDiagram fdPlotc = new FundamentalDiagram("Flow-speed", Quantity.FLOW, Quantity.SPEED, source, this.fdLine);
773
774
775 source.calculatePaintStateSafe(getSimulator().getSimulatorTime());
776
777
778 this.funamentalDiagrams.clear();
779 this.funamentalDiagrams.add(fdPlota);
780 this.funamentalDiagrams.add(fdPlotb);
781 this.funamentalDiagrams.add(fdPlotc);
782
783
784 Container fda = new SwingFundamentalDiagram(fdPlota).getContentPane();
785 Container fdb = new SwingFundamentalDiagram(fdPlotb).getContentPane();
786 Container fdc = new SwingFundamentalDiagram(fdPlotc).getContentPane();
787 Dimension preferredGraphSize = new Dimension(375, 230);
788 fda.setPreferredSize(preferredGraphSize);
789 fdb.setPreferredSize(preferredGraphSize);
790 fdc.setPreferredSize(preferredGraphSize);
791 this.graphPanel.setCell(fda, 0, 0);
792 this.graphPanel.setCell(fdb, 0, 1);
793 this.graphPanel.setCell(fdc, 1, 0);
794
795
796 this.trajectoryPanel.setPreferredSize(preferredGraphSize);
797 this.graphPanel.setCell(this.trajectoryPanel, 1, 1);
798
799
800 if (color != null)
801 {
802 fda.setBackground(color);
803 fdb.setBackground(color);
804 fdc.setBackground(color);
805 this.trajectoryPanel.setBackground(color);
806 }
807
808
809 fda.getParent().getParent().validate();
810
811
812 if (!getSimulator().isStartingOrRunning() && wasRunning)
813 {
814 getSimulator().start();
815 }
816 }
817
818
819
820
821 private final class DynamicFdLine implements FdLine
822 {
823
824
825 private Map<Quantity, double[]> map = new LinkedHashMap<>();
826
827 @Override
828 public double[] getValues(final Quantity quantity)
829 {
830 return this.map.get(quantity);
831 }
832
833 @Override
834 public String getName()
835 {
836 return "Theoretical";
837 }
838
839
840
841
842 public void update()
843 {
844
845 HarmonicMean<Speed, Double> meanSpeed = new HarmonicMean<>();
846 Speed carSpeed = FundamentalDiagramDemo.this.speedLimits.getSpeedLimit(Duration.ZERO).get().speed();
847 meanSpeed.add(carSpeed, 1.0 - FundamentalDiagramDemo.this.truckFraction);
848 Speed truckSpeed = Speed.min(carSpeed, new Speed(85.0, SpeedUnit.KM_PER_HOUR));
849 meanSpeed.add(truckSpeed, FundamentalDiagramDemo.this.truckFraction);
850
851
852 double meanLength =
853 4.19 * (1.0 - FundamentalDiagramDemo.this.truckFraction) + 12.0 * FundamentalDiagramDemo.this.truckFraction;
854
855
856 double vMax = meanSpeed.getMean();
857 double kCrit = 1000.0 / (vMax * FundamentalDiagramDemo.this.tMax.si + meanLength + 3.0);
858 vMax = vMax * 3.6;
859 double qMax = vMax * kCrit;
860 int kJam = (int) (1000.0 / (meanLength + 3.0));
861
862
863 double[] k = new double[kJam * 10 + 1];
864 double[] q = new double[kJam * 10 + 1];
865 double[] v = new double[kJam * 10 + 1];
866 for (int kk = 0; kk <= kJam * 10; kk++)
867 {
868 double kVal = kk / 10.0;
869 k[kk] = kVal;
870 if (kVal > kCrit)
871 {
872
873 q[kk] = qMax * (1.0 - (kVal - kCrit) / (kJam - kCrit));
874 v[kk] = q[kk] / k[kk];
875 }
876 else
877 {
878
879 v[kk] = vMax;
880 q[kk] = k[kk] * v[kk];
881 }
882 }
883
884
885 this.map.put(Quantity.DENSITY, k);
886 this.map.put(Quantity.FLOW, q);
887 this.map.put(Quantity.SPEED, v);
888 }
889
890 }
891
892 }