package org.opentrafficsim.demo.fd;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSlider;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.djunits.unit.FrequencyUnit;
import org.djunits.unit.SpeedUnit;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Direction;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Frequency;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.djutils.cli.CliUtil;
import org.djutils.exceptions.Try;
import org.djutils.means.HarmonicMean;
import org.opentrafficsim.base.parameters.ParameterException;
import org.opentrafficsim.base.parameters.ParameterTypes;
import org.opentrafficsim.base.parameters.Parameters;
import org.opentrafficsim.core.distributions.Generator;
import org.opentrafficsim.core.distributions.ProbabilityException;
import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
import org.opentrafficsim.core.geometry.OTSPoint3D;
import org.opentrafficsim.core.gtu.GTU;
import org.opentrafficsim.core.gtu.GTUDirectionality;
import org.opentrafficsim.core.gtu.GTUErrorHandler;
import org.opentrafficsim.core.gtu.GTUException;
import org.opentrafficsim.core.gtu.GTUType;
import org.opentrafficsim.core.gtu.GTUType.DEFAULTS;
import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
import org.opentrafficsim.core.idgenerator.IdGenerator;
import org.opentrafficsim.core.network.DirectedLinkPosition;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.LinkType;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.core.parameters.ParameterFactory;
import org.opentrafficsim.draw.graphs.FundamentalDiagram;
import org.opentrafficsim.draw.graphs.FundamentalDiagram.FdLine;
import org.opentrafficsim.draw.graphs.FundamentalDiagram.FdSource;
import org.opentrafficsim.draw.graphs.FundamentalDiagram.Quantity;
import org.opentrafficsim.draw.graphs.GraphCrossSection;
import org.opentrafficsim.draw.graphs.GraphPath;
import org.opentrafficsim.draw.graphs.TrajectoryPlot;
import org.opentrafficsim.draw.graphs.road.GraphLaneUtil;
import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
import org.opentrafficsim.road.gtu.generator.CFBARoomChecker;
import org.opentrafficsim.road.gtu.generator.GeneratorPositions;
import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBias;
import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator;
import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator.RoomChecker;
import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGTUCharacteristics;
import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGTUCharacteristicsGenerator;
import org.opentrafficsim.road.gtu.lane.VehicleModel;
import org.opentrafficsim.road.gtu.lane.perception.PerceptionFactory;
import org.opentrafficsim.road.gtu.lane.perception.categories.DirectInfrastructurePerception;
import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlannerFactory;
import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModelFactory;
import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlus;
import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusFactory;
import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLMRSPerceptionFactory;
import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRS;
import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRSFactory;
import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlannerFactory;
import org.opentrafficsim.road.network.OTSRoadNetwork;
import org.opentrafficsim.road.network.factory.LaneFactory;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import org.opentrafficsim.road.network.lane.DirectedLanePosition;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.LaneDirection;
import org.opentrafficsim.road.network.lane.LaneType;
import org.opentrafficsim.road.network.lane.OTSRoadNode;
import org.opentrafficsim.road.network.lane.Stripe.Permeable;
import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
import org.opentrafficsim.road.network.sampling.RoadSampler;
import org.opentrafficsim.swing.graphs.SwingFundamentalDiagram;
import org.opentrafficsim.swing.graphs.SwingTrajectoryPlot;
import org.opentrafficsim.swing.gui.OTSAnimationPanel;
import org.opentrafficsim.swing.gui.OTSAnimationPanel.DemoPanelPosition;
import org.opentrafficsim.swing.script.AbstractSimulationScript;
import nl.tudelft.simulation.dsol.swing.gui.TablePanel;
import nl.tudelft.simulation.jstats.distributions.DistNormal;
import nl.tudelft.simulation.jstats.streams.StreamInterface;
/**
* Demo showing what fundamental diagrams are. This demo is for education purposes.
*
* Copyright (c) 2020-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
*/
public class FundamentalDiagramDemo extends AbstractSimulationScript
{
/** */
private static final long serialVersionUID = 20200509L;
/** Dynamic demand. */
private Frequency demand = new Frequency(3500.0, FrequencyUnit.PER_HOUR);
/** Dynamic truck fraction. */
private double truckFraction = 0.05;
/** Speed limit. */
private Speed speedLimit = new Speed(120.0, SpeedUnit.KM_PER_HOUR);
/** Tmin. */
private Duration tMin = Duration.instantiateSI(0.56);
/** Tmax. */
private Duration tMax = Duration.instantiateSI(1.2);
/** Panel splitting controls from graphs. */
private JPanel splitPanel;
/** Panel with graphs. */
private TablePanel graphPanel;
/** Sampler. */
private RoadSampler sampler;
/** Selected cross-section. */
private String absoluteCrossSection1 = "1.50";
/** Second selected cross-section. */
private String absoluteCrossSection2 = "None";
/** Third selected cross-section. */
private String absoluteCrossSection3 = "None";
/** Fd line in graphs based on settings. */
@SuppressWarnings("synthetic-access")
private DynamicFdLine fdLine = new DynamicFdLine();
/** Fundamental diagrams that are updated when a setting is changed. */
private Set funamentalDiagrams = new LinkedHashSet<>();
/** Sources by name for each cross-section. */
private Map fdSourceMap = new LinkedHashMap<>();
/** Panel of trajectory graph. */
private Container trajectoryPanel;
/**
* Constructor.
*/
public FundamentalDiagramDemo()
{
super("FD Demo", "Fundamental diagram demo");
}
/**
* Main program.
* @param args String[]; the command line arguments (not used)
*/
public static void main(final String[] args)
{
FundamentalDiagramDemo demo = new FundamentalDiagramDemo();
try
{
CliUtil.changeOptionDefault(demo, "simulationTime", "360000s"); // 100h
CliUtil.execute(demo, args);
demo.start();
}
catch (Exception exception)
{
exception.printStackTrace();
}
}
/** {@inheritDoc} */
@Override
protected OTSRoadNetwork setupSimulation(final OTSSimulatorInterface sim) throws Exception
{
// Network
OTSRoadNetwork network = new OTSRoadNetwork("FD demo network", true, sim);
GTUType car = network.getGtuType(DEFAULTS.CAR);
GTUType truck = network.getGtuType(DEFAULTS.TRUCK);
OTSRoadNode nodeA = new OTSRoadNode(network, "Origin", new OTSPoint3D(0.0, 0.0), Direction.ZERO);
OTSRoadNode nodeB = new OTSRoadNode(network, "Lane-drop", new OTSPoint3D(1500.0, 0.0), Direction.ZERO);
OTSRoadNode nodeC = new OTSRoadNode(network, "Destination", new OTSPoint3D(2500.0, 0.0), Direction.ZERO);
LinkType linkType = network.getLinkType(LinkType.DEFAULTS.FREEWAY);
LaneKeepingPolicy policy = LaneKeepingPolicy.KEEPRIGHT;
Length laneWidth = Length.instantiateSI(3.5);
LaneType laneType = network.getLaneType(LaneType.DEFAULTS.FREEWAY);
Speed speedLim = new Speed(120.0, SpeedUnit.KM_PER_HOUR);
List lanesAB = new LaneFactory(network, nodeA, nodeB, linkType, sim, policy).leftToRight(3.0, laneWidth, laneType,
speedLim).addLanes(Permeable.BOTH, Permeable.BOTH).getLanes();
List lanesBC = new LaneFactory(network, nodeB, nodeC, linkType, sim, policy).leftToRight(2.0, laneWidth, laneType,
speedLim).addLanes(Permeable.BOTH).getLanes();
// Generator
// inter-arrival time generator
StreamInterface stream = sim.getReplication().getStream("generation");
Generator interarrivelTimeGenerator = new Generator()
{
@Override
public Duration draw() throws ProbabilityException, ParameterException
{
@SuppressWarnings("synthetic-access")
double mean = 1.0 / FundamentalDiagramDemo.this.demand.si;
return Duration.instantiateSI(-mean * Math.log(stream.nextDouble()));
}
};
// GTU characteristics generator
CarFollowingModelFactory carFollowingModelFactory = new IDMPlusFactory(stream);
PerceptionFactory perceptionFactory = new DefaultLMRSPerceptionFactory();
LaneBasedTacticalPlannerFactory tacticalPlannerFactory = new LMRSFactory(carFollowingModelFactory,
perceptionFactory);
DistNormal fSpeed = new DistNormal(stream, 123.7 / 120.0, 12.0 / 120.0);
ParameterFactory parametersFactory = new ParameterFactory()
{
@SuppressWarnings("synthetic-access")
@Override
public void setValues(final Parameters parameters, final GTUType gtuType) throws ParameterException
{
if (gtuType.equals(truck))
{
parameters.setParameter(ParameterTypes.A, Acceleration.instantiateSI(0.4));
}
else
{
parameters.setParameter(ParameterTypes.A, Acceleration.instantiateSI(2.0));
}
parameters.setParameter(ParameterTypes.FSPEED, fSpeed.draw()); // also for trucks due to low speed limit option
parameters.setParameter(ParameterTypes.TMIN, FundamentalDiagramDemo.this.tMin);
parameters.setParameter(ParameterTypes.TMAX, FundamentalDiagramDemo.this.tMax);
}
};
LaneBasedStrategicalRoutePlannerFactory laneBasedStrategicalPlannerFactory =
new LaneBasedStrategicalRoutePlannerFactory(tacticalPlannerFactory, parametersFactory);
LaneBasedGTUCharacteristicsGenerator laneBasedGTUCharacteristicsGenerator = new LaneBasedGTUCharacteristicsGenerator()
{
@Override
public LaneBasedGTUCharacteristics draw() throws ProbabilityException, ParameterException, GTUException
{
@SuppressWarnings("synthetic-access")
GTUType gtuType = stream.nextDouble() > FundamentalDiagramDemo.this.truckFraction ? car : truck;
return new LaneBasedGTUCharacteristics(GTUType.defaultCharacteristics(gtuType, network, stream),
laneBasedStrategicalPlannerFactory, null, nodeA, nodeC, VehicleModel.MINMAX);
}
};
// generator positions
Set initialPosition = new LinkedHashSet<>();
for (Lane lane : lanesAB)
{
initialPosition.add(new DirectedLanePosition(lane, Length.ZERO, GTUDirectionality.DIR_PLUS));
}
LaneBiases biases = new LaneBiases();
biases.addBias(car, LaneBias.bySpeed(new Speed(130.0, SpeedUnit.KM_PER_HOUR), new Speed(70.0, SpeedUnit.KM_PER_HOUR)));
biases.addBias(truck, LaneBias.TRUCK_RIGHT);
GeneratorPositions generatorPositions = GeneratorPositions.create(initialPosition, stream, biases);
// room checker
RoomChecker roomChecker = new CFBARoomChecker();
// id generator
IdGenerator idGenerator = new IdGenerator("");
// generator
LaneBasedGTUGenerator generator = new LaneBasedGTUGenerator("generator", interarrivelTimeGenerator,
laneBasedGTUCharacteristicsGenerator, generatorPositions, network, sim, roomChecker, idGenerator);
generator.setErrorHandler(GTUErrorHandler.DELETE);
generator.setInstantaneousLaneChange(true);
generator.setNoLaneChangeDistance(Length.instantiateSI(100.0));
// Sinks
for (Lane lane : lanesBC)
{
new SinkSensor(lane, lane.getLength(), GTUDirectionality.DIR_PLUS, sim);
}
return network;
}
/** {@inheritDoc} */
@Override
protected void setupDemo(final OTSAnimationPanel animationPanel, final OTSRoadNetwork net)
{
this.fdLine.update();
// Demo panel
animationPanel.createDemoPanel(DemoPanelPosition.BOTTOM);
animationPanel.getDemoPanel().setPreferredSize(new Dimension(1000, 500));
this.splitPanel = new JPanel(); // controls vs. graphs
JPanel controlPanel = new JPanel();
this.splitPanel.setLayout(new BoxLayout(this.splitPanel, BoxLayout.X_AXIS));
this.splitPanel.add(controlPanel);
animationPanel.getDemoPanel().add(this.splitPanel);
// Control panel
controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
controlPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
controlPanel.setPreferredSize(new Dimension(250, 500));
Dimension controlSize = new Dimension(250, 0);
int strutSize = 20;
// cross section dropdown
JLabel crossSectionLabel = new JLabel("Cross-section location [km]");
crossSectionLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
crossSectionLabel.setMinimumSize(controlSize);
controlPanel.add(crossSectionLabel);
List list = new ArrayList<>();
for (int i = 250; i <= 2250; i += 250)
{
list.add(String.format("%.2f", i / 1000.0));
}
JComboBox crossSectionMenu = new JComboBox(list.toArray(new String[0]));
Dimension crossSectionMenuSize = new Dimension(250, 25);
crossSectionMenu.setMinimumSize(crossSectionMenuSize);
crossSectionMenu.setMaximumSize(crossSectionMenuSize);
crossSectionMenu.setSelectedIndex(5);
crossSectionMenu.addActionListener(new ActionListener()
{
@SuppressWarnings({"synthetic-access", "unchecked"})
@Override
public void actionPerformed(final ActionEvent e)
{
FundamentalDiagramDemo.this.absoluteCrossSection1 = (String) ((JComboBox) e.getSource())
.getSelectedItem();
createFundamentalDiagramsForCrossSections();
}
});
controlPanel.add(crossSectionMenu);
// 2nd drop down
list = new ArrayList<>();
list.add("None");
for (int i = 250; i <= 2250; i += 250)
{
list.add(String.format("%.2f", i / 1000.0));
}
crossSectionMenu = new JComboBox(list.toArray(new String[0]));
crossSectionMenu.setMinimumSize(crossSectionMenuSize);
crossSectionMenu.setMaximumSize(crossSectionMenuSize);
crossSectionMenu.setSelectedIndex(0);
crossSectionMenu.addActionListener(new ActionListener()
{
@SuppressWarnings({"synthetic-access", "unchecked"})
@Override
public void actionPerformed(final ActionEvent e)
{
FundamentalDiagramDemo.this.absoluteCrossSection2 = (String) ((JComboBox) e.getSource())
.getSelectedItem();
createFundamentalDiagramsForCrossSections();
}
});
controlPanel.add(crossSectionMenu);
// 3rd drop down
crossSectionMenu = new JComboBox(list.toArray(new String[0]));
crossSectionMenu.setMinimumSize(crossSectionMenuSize);
crossSectionMenu.setMaximumSize(crossSectionMenuSize);
crossSectionMenu.setSelectedIndex(0);
crossSectionMenu.addActionListener(new ActionListener()
{
@SuppressWarnings({"synthetic-access", "unchecked"})
@Override
public void actionPerformed(final ActionEvent e)
{
FundamentalDiagramDemo.this.absoluteCrossSection3 = (String) ((JComboBox) e.getSource())
.getSelectedItem();
createFundamentalDiagramsForCrossSections();
}
});
controlPanel.add(crossSectionMenu);
// spacer
controlPanel.add(Box.createVerticalStrut(strutSize));
// reset button
JButton reset = new JButton("Clear data & graphs");
reset.setAlignmentX(Component.CENTER_ALIGNMENT);
reset.addActionListener(new ActionListener()
{
@SuppressWarnings("synthetic-access")
@Override
public void actionPerformed(final ActionEvent e)
{
clearDataAndGraphs();
}
});
controlPanel.add(reset);
// spacer
controlPanel.add(Box.createVerticalStrut(strutSize));
// demand
JLabel demandLabel = new JLabel("Demand [veh/h]");
demandLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
demandLabel.setPreferredSize(controlSize);
controlPanel.add(demandLabel);
JSlider demandSlider = new JSlider(500, 5000, 3500);
demandSlider.setPreferredSize(controlSize);
demandSlider.setSnapToTicks(true);
demandSlider.setMinorTickSpacing(250);
demandSlider.setMajorTickSpacing(1000);
demandSlider.setPaintTicks(true);
demandSlider.setPaintLabels(true);
demandSlider.setToolTipText("Demand [veh/h]");
demandSlider.addChangeListener(new ChangeListener()
{
@SuppressWarnings("synthetic-access")
@Override
public void stateChanged(final ChangeEvent e)
{
double value = ((JSlider) e.getSource()).getValue();
FundamentalDiagramDemo.this.demand = new Frequency(value, FrequencyUnit.PER_HOUR);
}
});
controlPanel.add(demandSlider);
// spacer
controlPanel.add(Box.createVerticalStrut(strutSize));
// truck percentage
JLabel truckLabel = new JLabel("Truck percentage [%]");
truckLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
truckLabel.setPreferredSize(controlSize);
controlPanel.add(truckLabel);
JSlider truckSlider = new JSlider(0, 30, 5);
truckSlider.setPreferredSize(controlSize);
truckSlider.setSnapToTicks(true);
truckSlider.setMinorTickSpacing(5);
truckSlider.setMajorTickSpacing(10);
truckSlider.setPaintTicks(true);
truckSlider.setPaintLabels(true);
truckSlider.setToolTipText("Truck percentage [%]");
truckSlider.addChangeListener(new ChangeListener()
{
@SuppressWarnings("synthetic-access")
@Override
public void stateChanged(final ChangeEvent e)
{
double value = ((JSlider) e.getSource()).getValue() / 100.0;
FundamentalDiagramDemo.this.truckFraction = value;
FundamentalDiagramDemo.this.fdLine.update();
notifyPlotsChanged();
}
});
controlPanel.add(truckSlider);
// spacer
controlPanel.add(Box.createVerticalStrut(strutSize));
// Tmax
JLabel tLabel = new JLabel("Max. headway [s]");
tLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
tLabel.setPreferredSize(controlSize);
controlPanel.add(tLabel);
JSlider tSlider = new JSlider(10, 20, 12);
Dictionary labels = new Hashtable<>();
for (int i = 10; i <= 20; i += 2)
{
labels.put(i, new JLabel(String.format("%.1f", i / 10.0)));
}
tSlider.setLabelTable(labels);
tSlider.setPreferredSize(controlSize);
tSlider.setSnapToTicks(true);
tSlider.setMinorTickSpacing(1);
tSlider.setMajorTickSpacing(2);
tSlider.setPaintTicks(true);
tSlider.setPaintLabels(true);
tSlider.setToolTipText("Max. headway [s]");
tSlider.addChangeListener(new ChangeListener()
{
@SuppressWarnings("synthetic-access")
@Override
public void stateChanged(final ChangeEvent e)
{
double value = ((JSlider) e.getSource()).getValue() / 10.0;
FundamentalDiagramDemo.this.tMin = Duration.instantiateSI((0.56 / 1.2) * value);
FundamentalDiagramDemo.this.tMax = Duration.instantiateSI(value);
FundamentalDiagramDemo.this.fdLine.update();
notifyPlotsChanged();
for (GTU gtu : getNetwork().getGTUs())
{
try
{
gtu.getParameters().setParameter(ParameterTypes.TMIN, FundamentalDiagramDemo.this.tMin);
gtu.getParameters().setParameter(ParameterTypes.TMAX, FundamentalDiagramDemo.this.tMax);
}
catch (ParameterException exception)
{
System.err.println("Unable to set headway parameter.");
}
}
}
});
controlPanel.add(tSlider);
// spacer
controlPanel.add(Box.createVerticalStrut(strutSize));
// V max
JLabel vLabel = new JLabel("Speed limit [km/h]");
vLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
vLabel.setPreferredSize(controlSize);
controlPanel.add(vLabel);
JSlider vSlider = new JSlider(80, 130, 120);
vSlider.setPreferredSize(controlSize);
vSlider.setSnapToTicks(true);
vSlider.setMinorTickSpacing(10);
vSlider.setMajorTickSpacing(10);
vSlider.setPaintTicks(true);
vSlider.setPaintLabels(true);
vSlider.setToolTipText("Speed limit [km/h]");
vSlider.addChangeListener(new ChangeListener()
{
@SuppressWarnings("synthetic-access")
@Override
public void stateChanged(final ChangeEvent e)
{
FundamentalDiagramDemo.this.speedLimit = new Speed(((JSlider) e.getSource()).getValue(), SpeedUnit.KM_PER_HOUR);
FundamentalDiagramDemo.this.fdLine.update();
notifyPlotsChanged();
for (Link link : getNetwork().getLinkMap().values())
{
for (Lane lane : ((CrossSectionLink) link).getLanes())
{
lane.setSpeedLimit(getNetwork().getGtuType(DEFAULTS.VEHICLE), FundamentalDiagramDemo.this.speedLimit);
}
}
for (GTU gtu : getNetwork().getGTUs())
{
DirectInfrastructurePerception infra;
try
{
infra = (DirectInfrastructurePerception) gtu.getTacticalPlanner().getPerception().getPerceptionCategory(
InfrastructurePerception.class);
// hack to reset the perceived speed limit cache
Field field = DirectInfrastructurePerception.class.getDeclaredField("root");
field.setAccessible(true);
field.set(infra, null);
}
catch (OperationalPlanException | NoSuchFieldException | SecurityException | IllegalArgumentException
| IllegalAccessException exception)
{
System.err.println("Unable to update perceived speed limit.");
}
}
}
});
controlPanel.add(vSlider);
// Initiate graphs
clearDataAndGraphs();
}
/**
* Response when settings were changed that affect the shape of the theoretical fundamental diagram, i.e. the FD line.
*/
void notifyPlotsChanged()
{
for (FundamentalDiagram diagram : this.funamentalDiagrams)
{
diagram.notifyPlotChange();
}
}
/**
* Response to clear data button.
*/
private void clearDataAndGraphs()
{
// creating a sampler (and graphs) while running gives issues with scheduling in the past (sometimes)
boolean wasRunning = getSimulator().isStartingOrRunning();
if (wasRunning)
{
getSimulator().stop();
}
// new sampler to loose all data
this.sampler = new RoadSampler(getNetwork());
// create fundamental diagram source for each cross section (plots are (re)created in setCrossSections())
for (int i = 250; i <= 2250; i += 250)
{
List names = new ArrayList<>();
names.add("Left");
names.add("Right");
Length lanePosition;
String linkId;
if (i >= 1500.0)
{
lanePosition = Length.instantiateSI(i - 1500.0);
linkId = "Lane-dropDestination";
}
else
{
names.add(1, "Middle");
lanePosition = Length.instantiateSI(i);
linkId = "OriginLane-drop";
}
DirectedLinkPosition linkPosition = new DirectedLinkPosition(getNetwork().getLink(linkId), lanePosition,
GTUDirectionality.DIR_PLUS);
GraphCrossSection crossSection;
try
{
crossSection = GraphLaneUtil.createCrossSection(names, linkPosition);
}
catch (NetworkException exception)
{
throw new RuntimeException("Unable to create cross section.", exception);
}
Duration aggregationTime = Duration.instantiateSI(30.0);
FdSource source = FundamentalDiagram.sourceFromSampler(this.sampler, crossSection, true, aggregationTime, false);
this.fdSourceMap.put(String.format("%.2f", i / 1000.0), source);
}
// create sampler plot
List names = new ArrayList<>();
names.add("Left lane");
names.add("Middle lane");
names.add("Right lane");
List firstLanes = new ArrayList<>();
for (Lane lane : ((CrossSectionLink) getNetwork().getLink("OriginLane-drop")).getLanes())
{
firstLanes.add(new LaneDirection(lane, GTUDirectionality.DIR_PLUS));
}
GraphPath path = Try.assign(() -> GraphLaneUtil.createPath(names, firstLanes), "");
TrajectoryPlot trajectoryPlot = new TrajectoryPlot("Trajectories", Duration.instantiateSI(5.0), getSimulator(),
this.sampler.getSamplerData(), path);
trajectoryPlot.updateFixedDomainRange(true);
SwingTrajectoryPlot swingTrajectoryPlot = new SwingTrajectoryPlot(trajectoryPlot)
{
/** */
private static final long serialVersionUID = 20200516L;
/** {@inheritDoc} */
@Override
protected void addPopUpMenuItems(final JPopupMenu popupMenu)
{
// disable
}
};
this.trajectoryPanel = swingTrajectoryPlot.getContentPane();
// reset simulator state
if (!getSimulator().isStartingOrRunning() && wasRunning)
{
getSimulator().start();
}
// create fundamental diagrams
createFundamentalDiagramsForCrossSections();
}
/**
* Creates the fundamental diagrams based on the selected cross-sections.
*/
private void createFundamentalDiagramsForCrossSections()
{
// avoid update scheduling in the past as simulator is running during creation
boolean wasRunning = getSimulator().isStartingOrRunning();
if (wasRunning)
{
getSimulator().stop();
}
// keep color of selected theme in new GUI elements
Color color = null;
if (this.graphPanel != null)
{
color = this.graphPanel.getBackground();
// remove previous graphs
this.splitPanel.remove(this.graphPanel);
}
// create new panel for graphs
this.graphPanel = new TablePanel(2, 2);
this.graphPanel.setBorder(new EmptyBorder(0, 0, 20, 0));
if (color != null)
{
this.graphPanel.setBackground(color);
}
this.splitPanel.add(this.graphPanel);
// compose a combined source if required
FdSource source;
if (this.absoluteCrossSection2.equals("None") && this.absoluteCrossSection3.equals("None"))
{
source = this.fdSourceMap.get(this.absoluteCrossSection1);
source.clearFundamentalDiagrams();
}
else
{
Map sources = new LinkedHashMap<>();
sources.put(this.absoluteCrossSection1 + "km", this.fdSourceMap.get(this.absoluteCrossSection1));
if (!this.absoluteCrossSection2.equals("None"))
{
sources.put(this.absoluteCrossSection2 + "km", this.fdSourceMap.get(this.absoluteCrossSection2));
}
if (!this.absoluteCrossSection3.equals("None"))
{
sources.put(this.absoluteCrossSection3 + "km", this.fdSourceMap.get(this.absoluteCrossSection3));
}
for (FdSource subSource : sources.values())
{
subSource.clearFundamentalDiagrams();
}
source = FundamentalDiagram.combinedSource(sources);
}
// because "Aggregate" and "Theoretical" looks ugly in the legend, we set the actual location as legend label
source.setAggregateName(this.absoluteCrossSection1);
// create the fundamental diagrams
FundamentalDiagram fdPlota = new FundamentalDiagram("Density-speed", Quantity.DENSITY, Quantity.SPEED, getSimulator(),
source, this.fdLine);
FundamentalDiagram fdPlotb = new FundamentalDiagram("Density-flow", Quantity.DENSITY, Quantity.FLOW, getSimulator(),
source, this.fdLine);
FundamentalDiagram fdPlotc = new FundamentalDiagram("Flow-speed", Quantity.FLOW, Quantity.SPEED, getSimulator(), source,
this.fdLine);
// recalculate over past data
source.recalculate(getSimulator().getSimulatorTime());
// store graphs so changes to setting may affect the graphs
this.funamentalDiagrams.clear();
this.funamentalDiagrams.add(fdPlota);
this.funamentalDiagrams.add(fdPlotb);
this.funamentalDiagrams.add(fdPlotc);
// create swing plots and add them to the graph panel
Container fda = new SwingFundamentalDiagramNoControl(fdPlota).getContentPane();
Container fdb = new SwingFundamentalDiagramNoControl(fdPlotb).getContentPane();
Container fdc = new SwingFundamentalDiagramNoControl(fdPlotc).getContentPane();
Dimension preferredGraphSize = new Dimension(375, 230);
fda.setPreferredSize(preferredGraphSize);
fdb.setPreferredSize(preferredGraphSize);
fdc.setPreferredSize(preferredGraphSize);
this.graphPanel.setCell(fda, 0, 0);
this.graphPanel.setCell(fdb, 0, 1);
this.graphPanel.setCell(fdc, 1, 0);
// also add the trajectory panel (which hasn't changed but should be moved to the new graphs panel)
this.trajectoryPanel.setPreferredSize(preferredGraphSize);
this.graphPanel.setCell(this.trajectoryPanel, 1, 1);
// set theme color of fundamental diagrams too (the little bar below where the mouse-over info is shown is visible)
if (color != null)
{
fda.setBackground(color);
fdb.setBackground(color);
fdc.setBackground(color);
this.trajectoryPanel.setBackground(color);
}
// reorganize panels
fda.getParent().getParent().validate();
// reset simulator state
if (!getSimulator().isStartingOrRunning() && wasRunning)
{
getSimulator().start();
}
}
/**
* Class to disable aggregation period and update frequency.
*/
private class SwingFundamentalDiagramNoControl extends SwingFundamentalDiagram
{
/** */
private static final long serialVersionUID = 20200516L;
/**
* @param plot FundamentalDiagram; fundamental diagram
*/
SwingFundamentalDiagramNoControl(final FundamentalDiagram plot)
{
super(plot);
}
/** {@inheritDoc} */
@Override
protected void addPopUpMenuItems(final JPopupMenu popupMenu)
{
// disable
}
}
/**
* Fundamental diagram line class based on local settings.
*/
private class DynamicFdLine implements FdLine
{
/** Map of points for each quantity. */
private Map map = new LinkedHashMap<>();
/** {@inheritDoc} */
@Override
public double[] getValues(final Quantity quantity)
{
return this.map.get(quantity);
}
/** {@inheritDoc} */
@Override
public String getName()
{
return "Theoretical";
}
/**
* Recalculates the FD based on input parameters.
*/
@SuppressWarnings("synthetic-access")
public void update()
{
// harmonic mean of desired speed of cars and trucks
HarmonicMean meanSpeed = new HarmonicMean<>();
Speed carSpeed = FundamentalDiagramDemo.this.speedLimit.times(123.7 / 120.0);
meanSpeed.add(carSpeed, 1.0 - FundamentalDiagramDemo.this.truckFraction);
Speed truckSpeed = Speed.min(carSpeed, new Speed(85.0, SpeedUnit.KM_PER_HOUR));
meanSpeed.add(truckSpeed, FundamentalDiagramDemo.this.truckFraction);
// mean of lengths
double meanLength = 4.19 * (1.0 - FundamentalDiagramDemo.this.truckFraction) + 12.0
* FundamentalDiagramDemo.this.truckFraction;
// calculate triangular FD parameters
double vMax = meanSpeed.getMean();
double kCrit = 1000.0 / (vMax * FundamentalDiagramDemo.this.tMax.si + meanLength + 3.0);
vMax = vMax * 3.6;
double qMax = vMax * kCrit;
int kJam = (int) (1000.0 / (meanLength + 3.0));
// initialize and fill arrays for each quantity
double[] k = new double[kJam * 10 + 1];
double[] q = new double[kJam * 10 + 1];
double[] v = new double[kJam * 10 + 1];
for (int kk = 0; kk <= kJam * 10; kk++)
{
double kVal = kk / 10.0;
k[kk] = kVal;
if (kVal > kCrit)
{
// congestion branch
q[kk] = qMax * (1.0 - (kVal - kCrit) / (kJam - kCrit));
v[kk] = q[kk] / k[kk];
}
else
{
// free-flow branch
v[kk] = vMax;
q[kk] = k[kk] * v[kk];
}
}
// cache values
this.map.put(Quantity.DENSITY, k);
this.map.put(Quantity.FLOW, q);
this.map.put(Quantity.SPEED, v);
}
}
}