FundamentalDiagram.java

  1. package org.opentrafficsim.graphs;

  2. import java.awt.BorderLayout;
  3. import java.awt.event.ActionEvent;
  4. import java.awt.event.ActionListener;
  5. import java.io.Serializable;
  6. import java.util.ArrayList;

  7. import javax.swing.ButtonGroup;
  8. import javax.swing.JFrame;
  9. import javax.swing.JLabel;
  10. import javax.swing.JMenu;
  11. import javax.swing.JPopupMenu;
  12. import javax.swing.JRadioButtonMenuItem;
  13. import javax.swing.SwingConstants;
  14. import javax.swing.event.EventListenerList;

  15. import org.djunits.unit.FrequencyUnit;
  16. import org.djunits.unit.LinearDensityUnit;
  17. import org.djunits.unit.SpeedUnit;
  18. import org.djunits.value.vdouble.scalar.Duration;
  19. import org.djunits.value.vdouble.scalar.Frequency;
  20. import org.djunits.value.vdouble.scalar.Length;
  21. import org.djunits.value.vdouble.scalar.LinearDensity;
  22. import org.djunits.value.vdouble.scalar.Speed;
  23. import org.djunits.value.vdouble.scalar.Time;
  24. import org.jfree.chart.ChartFactory;
  25. import org.jfree.chart.ChartPanel;
  26. import org.jfree.chart.JFreeChart;
  27. import org.jfree.chart.StandardChartTheme;
  28. import org.jfree.chart.axis.NumberAxis;
  29. import org.jfree.chart.axis.ValueAxis;
  30. import org.jfree.chart.event.AxisChangeEvent;
  31. import org.jfree.chart.labels.XYItemLabelGenerator;
  32. import org.jfree.chart.plot.PlotOrientation;
  33. import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
  34. import org.jfree.data.DomainOrder;
  35. import org.jfree.data.general.DatasetChangeEvent;
  36. import org.jfree.data.general.DatasetChangeListener;
  37. import org.jfree.data.general.DatasetGroup;
  38. import org.jfree.data.xy.XYDataset;
  39. import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
  40. import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
  41. import org.opentrafficsim.core.gtu.GTUException;
  42. import org.opentrafficsim.core.gtu.RelativePosition;
  43. import org.opentrafficsim.core.network.NetworkException;
  44. import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
  45. import org.opentrafficsim.road.network.lane.CrossSectionElement;
  46. import org.opentrafficsim.road.network.lane.Lane;
  47. import org.opentrafficsim.road.network.lane.object.sensor.AbstractSensor;

  48. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  49. import nl.tudelft.simulation.language.Throw;

  50. /**
  51.  * The Fundamental Diagram Graph; see <a href="http://en.wikipedia.org/wiki/Fundamental_diagram_of_traffic_flow"> Wikipedia:
  52.  * http://en.wikipedia.org/wiki/Fundamental_diagram_of_traffic_flow</a>.
  53.  * <p>
  54.  * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  55.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  56.  * <p>
  57.  * $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
  58.  * initial version Jul 31, 2014 <br>
  59.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  60.  */
  61. public class FundamentalDiagram extends JFrame implements XYDataset, ActionListener, Serializable
  62. {
  63.     /** */
  64.     private static final long serialVersionUID = 20140701L;

  65.     /** The ChartPanel for this Fundamental Diagram. */
  66.     private JFreeChart chartPanel;

  67.     /** Caption for this Fundamental Diagram. */
  68.     private final String caption;

  69.     /** Position of this Fundamental Diagram sensor. */
  70.     private final Length position;

  71.     /** Area to show status information. */
  72.     private final JLabel statusLabel;

  73.     /** Sample duration of the detector that generates this Fundamental Diagram. */
  74.     private final Duration aggregationTime;

  75.     /**
  76.      * @return aggregationTime
  77.      */
  78.     public final Duration getAggregationTime()
  79.     {
  80.         return this.aggregationTime;
  81.     }

  82.     /** Storage for the Samples. */
  83.     private ArrayList<Sample> samples = new ArrayList<Sample>();

  84.     /** Definition of the density axis. */
  85.     private Axis densityAxis = new Axis(new LinearDensity(0, LinearDensityUnit.PER_KILOMETER),
  86.             new LinearDensity(200, LinearDensityUnit.PER_KILOMETER), null, 0d, "Density [veh/km]", "Density",
  87.             "density %.1f veh/km");

  88.     /**
  89.      * @return densityAxis
  90.      */
  91.     public final Axis getDensityAxis()
  92.     {
  93.         return this.densityAxis;
  94.     }

  95.     /** Definition of the speed axis. */
  96.     private Axis speedAxis = new Axis(new Speed(0, SpeedUnit.KM_PER_HOUR), new Speed(180, SpeedUnit.KM_PER_HOUR), null, 0d,
  97.             "Speed [km/h]", "Speed", "speed %.0f km/h");

  98.     /**
  99.      * @return speedAxis
  100.      */
  101.     public final Axis getSpeedAxis()
  102.     {
  103.         return this.speedAxis;
  104.     }

  105.     /**
  106.      * @return flowAxis
  107.      */
  108.     public final Axis getFlowAxis()
  109.     {
  110.         return this.flowAxis;
  111.     }

  112.     /** Definition of the flow axis. */
  113.     private Axis flowAxis = new Axis(new Frequency(0, FrequencyUnit.PER_HOUR), new Frequency(3000d, FrequencyUnit.HERTZ), null,
  114.             0d, "Flow [veh/h]", "Flow", "flow %.0f veh/h");

  115.     /** The currently shown X-axis. */
  116.     private Axis xAxis;

  117.     /** The currently shown Y-axis. */
  118.     private Axis yAxis;

  119.     /** List of parties interested in changes of this ContourPlot. */
  120.     private transient EventListenerList listenerList = new EventListenerList();

  121.     /** Not used internally. */
  122.     private DatasetGroup datasetGroup = null;

  123.     /**
  124.      * Retrieve the format string for the Y axis.
  125.      * @return format string
  126.      */
  127.     public final String getYAxisFormat()
  128.     {
  129.         return this.yAxis.getFormat();
  130.     }

  131.     /**
  132.      * Retrieve the format string for the X axis.
  133.      * @return format string
  134.      */
  135.     public final String getXAxisFormat()
  136.     {
  137.         return this.xAxis.getFormat();
  138.     }

  139.     /**
  140.      * Graph a Fundamental Diagram.
  141.      * @param caption String; the caption shown above the graphing area.
  142.      * @param aggregationTime DoubleScalarRel&lt;TimeUnit&gt;; the aggregation of the detector that generates the data for this
  143.      *            Fundamental diagram
  144.      * @param lane Lane; the Lane on which the traffic will be sampled
  145.      * @param position DoubleScalarRel&lt;LengthUnit&gt;; longitudinal position of the detector on the Lane
  146.      * @param simulator the simulator
  147.      * @throws NetworkException on network inconsistency
  148.      */
  149.     public FundamentalDiagram(final String caption, final Duration aggregationTime, final Lane lane, final Length position,
  150.             final OTSDEVSSimulatorInterface simulator) throws NetworkException
  151.     {
  152.         if (aggregationTime.getSI() <= 0)
  153.         {
  154.             throw new Error("Aggregation time must be > 0 (got " + aggregationTime + ")");
  155.         }
  156.         this.aggregationTime = aggregationTime;
  157.         this.caption = caption;
  158.         this.position = position;
  159.         ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
  160.         this.chartPanel =
  161.                 ChartFactory.createXYLineChart(this.caption, "", "", this, PlotOrientation.VERTICAL, false, false, false);
  162.         FixCaption.fixCaption(this.chartPanel);
  163.         final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) this.chartPanel.getXYPlot().getRenderer();
  164.         renderer.setBaseLinesVisible(true);
  165.         renderer.setBaseShapesVisible(true);
  166.         renderer.setBaseItemLabelGenerator(new XYItemLabelGenerator()
  167.         {
  168.             @Override
  169.             public String generateLabel(final XYDataset dataset, final int series, final int item)
  170.             {
  171.                 return String.format("%.0fs", item * aggregationTime.getSI());
  172.             }
  173.         });
  174.         renderer.setBaseItemLabelsVisible(true);
  175.         final ChartPanel cp = new ChartPanel(this.chartPanel);
  176.         PointerHandler ph = new PointerHandler()
  177.         {
  178.             /** */
  179.             private static final long serialVersionUID = 20140000L;

  180.             /** {@inheritDoc} */
  181.             @Override
  182.             void updateHint(final double domainValue, final double rangeValue)
  183.             {
  184.                 if (Double.isNaN(domainValue))
  185.                 {
  186.                     setStatusText(" ");
  187.                     return;
  188.                 }
  189.                 String s1 = String.format(getXAxisFormat(), domainValue);
  190.                 String s2 = String.format(getYAxisFormat(), rangeValue);
  191.                 setStatusText(s1 + ", " + s2);
  192.             }

  193.         };
  194.         cp.addMouseMotionListener(ph);
  195.         cp.addMouseListener(ph);
  196.         cp.setMouseWheelEnabled(true);
  197.         final JMenu subMenu = new JMenu("Set layout");
  198.         final ButtonGroup group = new ButtonGroup();
  199.         final JRadioButtonMenuItem defaultItem = addMenuItem(subMenu, group, getDensityAxis(), this.flowAxis, true);
  200.         addMenuItem(subMenu, group, this.flowAxis, this.speedAxis, false);
  201.         addMenuItem(subMenu, group, this.densityAxis, this.speedAxis, false);
  202.         actionPerformed(new ActionEvent(this, 0, defaultItem.getActionCommand()));
  203.         final JPopupMenu popupMenu = cp.getPopupMenu();
  204.         popupMenu.insert(subMenu, 0);
  205.         this.add(cp, BorderLayout.CENTER);
  206.         this.statusLabel = new JLabel(" ", SwingConstants.CENTER);
  207.         this.add(this.statusLabel, BorderLayout.SOUTH);
  208.         new FundamentalDiagramSensor(lane, position, simulator);
  209.     }

  210.     /**
  211.      * Update the status text.
  212.      * @param newText String; the new text to show
  213.      */
  214.     public final void setStatusText(final String newText)
  215.     {
  216.         this.statusLabel.setText(newText);
  217.     }

  218.     /**
  219.      * Retrieve the position of the detector.
  220.      * @return Length; the position of the detector
  221.      */
  222.     public final Length getPosition()
  223.     {
  224.         return this.position;
  225.     }

  226.     /**
  227.      * Build one JRadioButtonMenuItem for the sub menu of the context menu.
  228.      * @param subMenu JMenu; the menu to which the new JRadioButtonMenuItem is added
  229.      * @param group ButtonGroup; the buttonGroup for the new JRadioButtonMenuItem
  230.      * @param xAxisToSelect Axis; the Axis that will become X-axis when this item is clicked
  231.      * @param yAxisToSelect Axis; the Axis that will become Y-axis when this item is clicked
  232.      * @param selected Boolean; if true, the new JRadioButtonMenuItem will be selected; if false, the new JRadioButtonMenuItem
  233.      *            will <b>not</b> be selected
  234.      * @return JRatioButtonMenuItem; the newly added item
  235.      */
  236.     private JRadioButtonMenuItem addMenuItem(final JMenu subMenu, final ButtonGroup group, final Axis xAxisToSelect,
  237.             final Axis yAxisToSelect, final boolean selected)
  238.     {
  239.         final JRadioButtonMenuItem item =
  240.                 new JRadioButtonMenuItem(yAxisToSelect.getShortName() + " / " + xAxisToSelect.getShortName());
  241.         item.setSelected(selected);
  242.         item.setActionCommand(yAxisToSelect.getShortName() + "/" + xAxisToSelect.getShortName());
  243.         item.addActionListener(this);
  244.         subMenu.add(item);
  245.         group.add(item);
  246.         return item;
  247.     }

  248.     /**
  249.      * Add the effect of one passing car to this Fundamental Diagram.
  250.      * @param gtu AbstractLaneBasedGTU; the GTU that passes the detection point
  251.      * @throws GTUException when the speed of the GTU cannot be assessed
  252.      */
  253.     public final void addData(final LaneBasedGTU gtu) throws GTUException
  254.     {
  255.         Time detectionTime = gtu.getSimulator().getSimulatorTime().getTime();
  256.         // Figure out the time bin
  257.         final int timeBin = (int) Math.floor(detectionTime.getSI() / this.aggregationTime.getSI());
  258.         // Extend storage if needed
  259.         while (timeBin >= this.samples.size())
  260.         {
  261.             this.samples.add(new Sample());
  262.         }
  263.         Sample sample = this.samples.get(timeBin);
  264.         sample.addData(gtu.getSpeed());
  265.     }

  266.     /**
  267.      * Set up a JFreeChart axis.
  268.      * @param valueAxis ValueAxis; the axis to set up
  269.      * @param axis Axis; the Axis that provides the data to setup the ValueAxis
  270.      */
  271.     private static void configureAxis(final ValueAxis valueAxis, final Axis axis)
  272.     {
  273.         valueAxis.setLabel("\u2192 " + axis.getName());
  274.         valueAxis.setRange(axis.getMinimumValue().getInUnit(), axis.getMaximumValue().getInUnit());
  275.     }

  276.     /**
  277.      * Redraw this TrajectoryGraph (after the underlying data has been changed, or to change axes).
  278.      */
  279.     public final void reGraph()
  280.     {
  281.         NumberAxis numberAxis = new NumberAxis();
  282.         configureAxis(numberAxis, this.xAxis);
  283.         this.chartPanel.getXYPlot().setDomainAxis(numberAxis);
  284.         this.chartPanel.getPlot().axisChanged(new AxisChangeEvent(numberAxis));
  285.         numberAxis = new NumberAxis();
  286.         configureAxis(numberAxis, this.yAxis);
  287.         this.chartPanel.getXYPlot().setRangeAxis(numberAxis);
  288.         this.chartPanel.getPlot().axisChanged(new AxisChangeEvent(numberAxis));
  289.         notifyListeners(new DatasetChangeEvent(this, null)); // This guess work actually works!
  290.     }

  291.     /**
  292.      * Notify interested parties of an event affecting this TrajectoryPlot.
  293.      * @param event DatasetChangedEvent
  294.      */
  295.     private void notifyListeners(final DatasetChangeEvent event)
  296.     {
  297.         for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
  298.         {
  299.             dcl.datasetChanged(event);
  300.         }
  301.     }

  302.     /** {@inheritDoc} */
  303.     @Override
  304.     public final int getSeriesCount()
  305.     {
  306.         return 1;
  307.     }

  308.     /** {@inheritDoc} */
  309.     @Override
  310.     public final Comparable<Integer> getSeriesKey(final int series)
  311.     {
  312.         return series;
  313.     }

  314.     /** {@inheritDoc} */
  315.     @SuppressWarnings("rawtypes")
  316.     @Override
  317.     public final int indexOf(final Comparable seriesKey)
  318.     {
  319.         if (seriesKey instanceof Integer)
  320.         {
  321.             return (Integer) seriesKey;
  322.         }
  323.         return -1;
  324.     }

  325.     /** {@inheritDoc} */
  326.     @Override
  327.     public final void addChangeListener(final DatasetChangeListener listener)
  328.     {
  329.         this.listenerList.add(DatasetChangeListener.class, listener);
  330.     }

  331.     /** {@inheritDoc} */
  332.     @Override
  333.     public final void removeChangeListener(final DatasetChangeListener listener)
  334.     {
  335.         this.listenerList.remove(DatasetChangeListener.class, listener);
  336.     }

  337.     /** {@inheritDoc} */
  338.     @Override
  339.     public final DatasetGroup getGroup()
  340.     {
  341.         return this.datasetGroup;
  342.     }

  343.     /** {@inheritDoc} */
  344.     @Override
  345.     public final void setGroup(final DatasetGroup group)
  346.     {
  347.         this.datasetGroup = group;
  348.     }

  349.     /** {@inheritDoc} */
  350.     @Override
  351.     public final DomainOrder getDomainOrder()
  352.     {
  353.         return DomainOrder.ASCENDING;
  354.     }

  355.     /** {@inheritDoc} */
  356.     @Override
  357.     public final int getItemCount(final int series)
  358.     {
  359.         return this.samples.size();
  360.     }

  361.     /**
  362.      * Retrieve a value from the recorded samples.
  363.      * @param item Integer; the rank number of the sample
  364.      * @param axis Axis; the axis that determines which quantity to retrieve
  365.      * @return Double; the requested value, or Double.NaN if the sample does not (yet) exist
  366.      */
  367.     private Double getSample(final int item, final Axis axis)
  368.     {
  369.         if (item >= this.samples.size())
  370.         {
  371.             return Double.NaN;
  372.         }
  373.         double result = this.samples.get(item).getValue(axis);
  374.         /*-
  375.         System.out.println(String.format("getSample(item=%d, axis=%s) returns %f", item, axis.name,
  376.                 result));
  377.          */
  378.         return result;
  379.     }

  380.     /** {@inheritDoc} */
  381.     @Override
  382.     public final Number getX(final int series, final int item)
  383.     {
  384.         return getXValue(series, item);
  385.     }

  386.     /** {@inheritDoc} */
  387.     @Override
  388.     public final double getXValue(final int series, final int item)
  389.     {
  390.         return getSample(item, this.xAxis);
  391.     }

  392.     /** {@inheritDoc} */
  393.     @Override
  394.     public final Number getY(final int series, final int item)
  395.     {
  396.         return getYValue(series, item);
  397.     }

  398.     /** {@inheritDoc} */
  399.     @Override
  400.     public final double getYValue(final int series, final int item)
  401.     {
  402.         return getSample(item, this.yAxis);
  403.     }

  404.     /** {@inheritDoc} */
  405.     @Override
  406.     public final String toString()
  407.     {
  408.         return "FundamentalDiagram [caption=" + this.caption + ", aggregationTime=" + this.aggregationTime + ", samples.size="
  409.                 + this.samples.size() + "]";
  410.     }

  411.     /**
  412.      * Storage for one sample of data collected by a point-detector that accumulates harmonic mean speed and flow.
  413.      */
  414.     class Sample implements Serializable
  415.     {
  416.         /** */
  417.         private static final long serialVersionUID = 20140000L;

  418.         /** Harmonic mean speed observed during this sample [m/s]. */
  419.         private double harmonicMeanSpeed;

  420.         /** Flow observed during this sample [veh/s]. */
  421.         private double flow;

  422.         /**
  423.          * Retrieve a value stored in this Sample.
  424.          * @param axis Axis; the axis along which the data is requested
  425.          * @return double; the retrieved value
  426.          */
  427.         public double getValue(final Axis axis)
  428.         {
  429.             if (axis == getDensityAxis())
  430.             {
  431.                 return this.flow * 3600 / getAggregationTime().getSI() / this.harmonicMeanSpeed;
  432.             }
  433.             else if (axis == getFlowAxis())
  434.             {
  435.                 return this.flow * 3600 / getAggregationTime().getSI();
  436.             }
  437.             else if (axis == getSpeedAxis())
  438.             {
  439.                 return this.harmonicMeanSpeed * 3600 / 1000;
  440.             }
  441.             else
  442.             {
  443.                 throw new Error("Sample.getValue: Can not identify axis");
  444.             }
  445.         }

  446.         /**
  447.          * Add one Car detection to this Sample.
  448.          * @param speed Speed; the detected speed
  449.          */
  450.         public void addData(final Speed speed)
  451.         {
  452.             double sumReciprocalSpeeds = 0;
  453.             if (this.flow > 0)
  454.             {
  455.                 sumReciprocalSpeeds = this.flow / this.harmonicMeanSpeed;
  456.             }
  457.             this.flow += 1;
  458.             sumReciprocalSpeeds += 1d / speed.getSI();
  459.             this.harmonicMeanSpeed = this.flow / sumReciprocalSpeeds;
  460.         }

  461.         /** {@inheritDoc} */
  462.         @Override
  463.         public final String toString()
  464.         {
  465.             return "Sample [harmonicMeanSpeed=" + this.harmonicMeanSpeed + ", flow=" + this.flow + "]";
  466.         }
  467.     }

  468.     /** {@inheritDoc} */
  469.     @SuppressFBWarnings("ES_COMPARING_STRINGS_WITH_EQ")
  470.     @Override
  471.     public final void actionPerformed(final ActionEvent actionEvent)
  472.     {
  473.         final String command = actionEvent.getActionCommand();
  474.         // System.out.println("command is \"" + command + "\"");
  475.         final String[] fields = command.split("[/]");
  476.         if (fields.length == 2)
  477.         {
  478.             for (String field : fields)
  479.             {
  480.                 if (field.equalsIgnoreCase(this.densityAxis.getShortName()))
  481.                 {
  482.                     if (field == fields[0])
  483.                     {
  484.                         this.yAxis = this.densityAxis;
  485.                     }
  486.                     else
  487.                     {
  488.                         this.xAxis = this.densityAxis;
  489.                     }
  490.                 }
  491.                 else if (field.equalsIgnoreCase(this.flowAxis.getShortName()))
  492.                 {
  493.                     if (field == fields[0])
  494.                     {
  495.                         this.yAxis = this.flowAxis;
  496.                     }
  497.                     else
  498.                     {
  499.                         this.xAxis = this.flowAxis;
  500.                     }
  501.                 }
  502.                 else if (field.equalsIgnoreCase(this.speedAxis.getShortName()))
  503.                 {
  504.                     if (field == fields[0])
  505.                     {
  506.                         this.yAxis = this.speedAxis;
  507.                     }
  508.                     else
  509.                     {
  510.                         this.xAxis = this.speedAxis;
  511.                     }
  512.                 }
  513.                 else
  514.                 {
  515.                     throw new Error("Cannot find axis name: " + field);
  516.                 }
  517.             }
  518.             reGraph();
  519.         }
  520.         else
  521.         {
  522.             throw new Error("Unknown ActionEvent");
  523.         }
  524.     }

  525.     /**
  526.      * Internal Sensor class.
  527.      * <p>
  528.      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  529.      * <br>
  530.      * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  531.      * <p>
  532.      * $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
  533.      * initial version feb. 2015 <br>
  534.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  535.      */
  536.     class FundamentalDiagramSensor extends AbstractSensor
  537.     {
  538.         /** */
  539.         private static final long serialVersionUID = 20150203L;

  540.         /**
  541.          * Construct a FundamentalDiagramSensor.
  542.          * @param lane Lane; the Lane on which the new FundamentalDiagramSensor is to be added
  543.          * @param longitudinalPosition Length; longitudinal position on the Lane of the new FundamentalDiagramSensor
  544.          * @param simulator simulator to allow animation
  545.          * @throws NetworkException on network inconsistency
  546.          */
  547.         FundamentalDiagramSensor(final Lane lane, final Length longitudinalPosition,
  548.                 final OTSDEVSSimulatorInterface simulator) throws NetworkException
  549.         {
  550.             super("FUNDAMENTAL_DIAGRAM_SENSOR@" + lane.toString(), lane, longitudinalPosition, RelativePosition.REFERENCE,
  551.                     simulator);
  552.         }

  553.         /** {@inheritDoc} */
  554.         @Override
  555.         public void triggerResponse(final LaneBasedGTU gtu)
  556.         {
  557.             try
  558.             {
  559.                 addData(gtu);
  560.             }
  561.             catch (GTUException exception)
  562.             {
  563.                 exception.printStackTrace(); // TODO
  564.             }
  565.         }

  566.         /** {@inheritDoc} */
  567.         public final String toString()
  568.         {
  569.             return "FundamentalDiagramSensor at " + getLongitudinalPosition();
  570.         }

  571.         /** {@inheritDoc} */
  572.         @Override
  573.         public FundamentalDiagramSensor clone(final CrossSectionElement newCSE, final OTSSimulatorInterface newSimulator,
  574.                 final boolean animation) throws NetworkException
  575.         {
  576.             Throw.when(!(newCSE instanceof Lane), NetworkException.class, "sensors can only be cloned for Lanes");
  577.             Throw.when(!(newSimulator instanceof OTSDEVSSimulatorInterface), NetworkException.class,
  578.                     "simulator should be a DEVSSimulator");
  579.             return new FundamentalDiagramSensor((Lane) newCSE, getLongitudinalPosition(),
  580.                     (OTSDEVSSimulatorInterface) newSimulator);
  581.         }

  582.     }

  583. }