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
109
110
111
112
113
114
115
116
117 public class FundamentalDiagramDemo extends AbstractSimulationScript
118 {
119
120
121 private Frequency demand = new Frequency(3500.0, FrequencyUnit.PER_HOUR);
122
123
124 private double truckFraction = 0.05;
125
126
127 private Speed speedLimit = new Speed(120.0, SpeedUnit.KM_PER_HOUR);
128
129
130 private Duration tMin = Duration.ofSI(0.56);
131
132
133 private Duration tMax = Duration.ofSI(1.2);
134
135
136 private JPanel splitPanel;
137
138
139 private TablePanel graphPanel;
140
141
142 private RoadSampler sampler;
143
144
145 private String absoluteCrossSection1 = "1.50";
146
147
148 private String absoluteCrossSection2 = "None";
149
150
151 private String absoluteCrossSection3 = "None";
152
153
154 @SuppressWarnings("synthetic-access")
155 private DynamicFdLine fdLine = new DynamicFdLine();
156
157
158 private TrajectoryPlot trajectoryPlot;
159
160
161 private Set<FundamentalDiagram> funamentalDiagrams = new LinkedHashSet<>();
162
163
164 private Map<String, FdSource> fdSourceMap = new LinkedHashMap<>();
165
166
167 private Container trajectoryPanel;
168
169
170 private PlotScheduler scheduler;
171
172
173
174
175 public FundamentalDiagramDemo()
176 {
177 super("FD Demo", "Fundamental diagram demo");
178 }
179
180
181
182
183
184 public static void main(final String[] args)
185 {
186 FundamentalDiagramDemo demo = new FundamentalDiagramDemo();
187 try
188 {
189 CliUtil.changeOptionDefault(demo, "simulationTime", "360000s");
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
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
224
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
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());
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
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 void setupDemo(final OtsAnimationPanel animationPanel, final RoadNetwork net)
306 {
307 this.fdLine.update();
308
309
310 animationPanel.createDemoPanel(DemoPanelPosition.BOTTOM);
311 animationPanel.getDemoPanel().setPreferredSize(new Dimension(1000, 500));
312 this.splitPanel = new JPanel();
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
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
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
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
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
392 controlPanel.add(Box.createVerticalStrut(strutSize));
393
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
407 controlPanel.add(Box.createVerticalStrut(strutSize));
408
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
433 controlPanel.add(Box.createVerticalStrut(strutSize));
434
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
461 controlPanel.add(Box.createVerticalStrut(strutSize));
462
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
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
519 controlPanel.add(Box.createVerticalStrut(strutSize));
520
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
554 this.scheduler = new OtsPlotScheduler(getSimulator());
555 clearDataAndGraphs();
556 }
557
558
559
560
561 void notifyPlotsChanged()
562 {
563 for (FundamentalDiagram diagram : this.funamentalDiagrams)
564 {
565 diagram.notifyPlotChange();
566 }
567 }
568
569
570
571
572 private void clearDataAndGraphs()
573 {
574
575 boolean wasRunning = getSimulator().isStartingOrRunning();
576 if (wasRunning)
577 {
578 getSimulator().stop();
579 }
580
581
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
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
623 if (this.trajectoryPlot != null)
624 {
625 this.scheduler.cancelEvent(this.trajectoryPlot);
626 }
627 this.funamentalDiagrams.forEach((fd) -> this.scheduler.cancelEvent(fd));
628
629
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
652 }
653 };
654 this.trajectoryPanel = swingTrajectoryPlot.getContentPane();
655
656
657 createFundamentalDiagramsForCrossSections();
658
659
660 if (!getSimulator().isStartingOrRunning() && wasRunning)
661 {
662 getSimulator().start();
663 }
664 }
665
666
667
668
669 private void createFundamentalDiagramsForCrossSections()
670 {
671
672 boolean wasRunning = getSimulator().isStartingOrRunning();
673 if (wasRunning)
674 {
675 getSimulator().stop();
676 }
677
678
679 Color color = null;
680 if (this.graphPanel != null)
681 {
682 color = this.graphPanel.getBackground();
683
684 this.splitPanel.remove(this.graphPanel);
685 }
686
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
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
726 source.setAggregateName(this.absoluteCrossSection1);
727
728
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
737 source.recalculate(getSimulator().getSimulatorTime());
738
739
740 this.funamentalDiagrams.clear();
741 this.funamentalDiagrams.add(fdPlota);
742 this.funamentalDiagrams.add(fdPlotb);
743 this.funamentalDiagrams.add(fdPlotc);
744
745
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
758 this.trajectoryPanel.setPreferredSize(preferredGraphSize);
759 this.graphPanel.setCell(this.trajectoryPanel, 1, 1);
760
761
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
771 fda.getParent().getParent().validate();
772
773
774 if (!getSimulator().isStartingOrRunning() && wasRunning)
775 {
776 getSimulator().start();
777 }
778 }
779
780
781
782
783 private class SwingFundamentalDiagramNoControl extends SwingFundamentalDiagram
784 {
785
786 private static final long serialVersionUID = 20251121L;
787
788
789
790
791 SwingFundamentalDiagramNoControl(final FundamentalDiagram plot)
792 {
793 super(plot);
794 }
795
796 @Override
797 protected void addPopUpMenuItems(final JPopupMenu popupMenu)
798 {
799
800 }
801 }
802
803
804
805
806 private final class DynamicFdLine implements FdLine
807 {
808
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
825
826 @SuppressWarnings("synthetic-access")
827 public void update()
828 {
829
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
837 double meanLength =
838 4.19 * (1.0 - FundamentalDiagramDemo.this.truckFraction) + 12.0 * FundamentalDiagramDemo.this.truckFraction;
839
840
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
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
858 q[kk] = qMax * (1.0 - (kVal - kCrit) / (kJam - kCrit));
859 v[kk] = q[kk] / k[kk];
860 }
861 else
862 {
863
864 v[kk] = vMax;
865 q[kk] = k[kk] * v[kk];
866 }
867 }
868
869
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 }