ODMatrix.java

  1. package org.opentrafficsim.road.gtu.strategical.od;

  2. import java.io.Serializable;
  3. import java.util.ArrayList;
  4. import java.util.HashMap;
  5. import java.util.HashSet;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Set;

  9. import org.djunits.unit.FrequencyUnit;
  10. import org.djunits.unit.TimeUnit;
  11. import org.djunits.value.StorageType;
  12. import org.djunits.value.ValueException;
  13. import org.djunits.value.vdouble.scalar.Duration;
  14. import org.djunits.value.vdouble.scalar.Frequency;
  15. import org.djunits.value.vdouble.vector.DurationVector;
  16. import org.djunits.value.vdouble.vector.FrequencyVector;
  17. import org.opentrafficsim.core.geometry.OTSPoint3D;
  18. import org.opentrafficsim.core.network.NetworkException;
  19. import org.opentrafficsim.core.network.Node;
  20. import org.opentrafficsim.core.network.OTSNetwork;
  21. import org.opentrafficsim.core.network.OTSNode;
  22. import org.opentrafficsim.core.network.route.Route;

  23. import nl.tudelft.simulation.language.Throw;

  24. /**
  25.  * The minimal OD matrix has 1 origin, 1 destination and 1 time period. More of each can be used. Further categorization of data
  26.  * is possible, i.e. for origin O to destination D, <i>for lane L, for route R and for vehicle class C</i>, the demand at time T
  27.  * is D. The further categorization is defined by an array of {@code Class}'s that define the categorization.
  28.  * <p>
  29.  * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  30.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
  31.  * <p>
  32.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 15, 2016 <br>
  33.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  34.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  35.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  36.  */
  37. public class ODMatrix implements Serializable
  38. {

  39.     /** */
  40.     private static final long serialVersionUID = 20160921L;

  41.     /** Id. */
  42.     private final String id;

  43.     /** Origin nodes. */
  44.     private final List<Node> origins;

  45.     /** Destination nodes. */
  46.     private final List<Node> destinations;

  47.     /** Categorization of demand data. */
  48.     private final Categorization categorization;

  49.     /** Global time vector. */
  50.     private final DurationVector globalTimeVector;

  51.     /** Global interpolation of the data. */
  52.     private final Interpolation globalInterpolation;

  53.     /** Demand data per origin and destination, and possibly further categorization. */
  54.     private final Map<Node, Map<Node, Map<Category, ODEntry>>> demandData = new HashMap<>();

  55.     /**
  56.      * Constructs an OD matrix.
  57.      * @param id id
  58.      * @param origins origin nodes
  59.      * @param destinations destination nodes
  60.      * @param categorization categorization of data
  61.      * @param globalTimeVector default time
  62.      * @param globalInterpolation interpolation of demand data
  63.      * @throws NullPointerException if any input is null
  64.      */
  65.     public ODMatrix(final String id, final List<Node> origins, final List<Node> destinations,
  66.         final Categorization categorization, final DurationVector globalTimeVector, final Interpolation globalInterpolation)
  67.     {
  68.         Throw.whenNull(id, "Id may not be null.");
  69.         Throw.when(origins == null || origins.contains(null), NullPointerException.class,
  70.             "Origin may not be or contain null.");
  71.         Throw.when(destinations == null || destinations.contains(null), NullPointerException.class,
  72.             "Destination may not be or contain null.");
  73.         Throw.whenNull(categorization, "Categorization may not be null.");
  74.         Throw.whenNull(globalTimeVector, "Global time vector may not be null.");
  75.         Throw.whenNull(globalInterpolation, "Global interpolation may not be null.");
  76.         this.id = id;
  77.         this.origins = new ArrayList<>(origins);
  78.         this.destinations = new ArrayList<>(destinations);
  79.         this.categorization = categorization;
  80.         this.globalTimeVector = globalTimeVector;
  81.         this.globalInterpolation = globalInterpolation;
  82.         // build empty OD
  83.         for (Node origin : origins)
  84.         {
  85.             Map<Node, Map<Category, ODEntry>> map = new HashMap<>();
  86.             for (Node destination : destinations)
  87.             {
  88.                 map.put(destination, new HashMap<>());
  89.             }
  90.             this.demandData.put(origin, map);
  91.         }
  92.     }

  93.     /**
  94.      * @return id.
  95.      */
  96.     public final String getId()
  97.     {
  98.         return this.id;
  99.     }

  100.     /**
  101.      * @return origins.
  102.      */
  103.     public final List<Node> getOrigins()
  104.     {
  105.         return new ArrayList<>(this.origins);
  106.     }

  107.     /**
  108.      * @return destinations.
  109.      */
  110.     public final List<Node> getDestinations()
  111.     {
  112.         return new ArrayList<>(this.destinations);
  113.     }

  114.     /**
  115.      * @return categorization.
  116.      */
  117.     public final Categorization getCategorization()
  118.     {
  119.         return this.categorization;
  120.     }

  121.     /**
  122.      * @return globalTimeVector.
  123.      */
  124.     public final DurationVector getGlobalTimeVector()
  125.     {
  126.         return this.globalTimeVector;
  127.     }

  128.     /**
  129.      * @return globalInterpolation.
  130.      */
  131.     public final Interpolation getGlobalInterpolation()
  132.     {
  133.         return this.globalInterpolation;
  134.     }

  135.     /**
  136.      * @param origin origin
  137.      * @param destination destination
  138.      * @param category category
  139.      * @param demand demand data, length has to be equal to the global time vector
  140.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  141.      * @throws IllegalArgumentException if the category does not belong to the categorization
  142.      * @throws NullPointerException if an input is null
  143.      */
  144.     public final void putDemandVector(final Node origin, final Node destination, final Category category,
  145.         final FrequencyVector demand)
  146.     {
  147.         putDemandVector(origin, destination, category, demand, this.globalTimeVector, this.globalInterpolation);
  148.     }

  149.     /**
  150.      * @param origin origin
  151.      * @param destination destination
  152.      * @param category category
  153.      * @param demand demand data, length has to be equal to the time vector
  154.      * @param timeVector time vector
  155.      * @param interpolation interpolation
  156.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  157.      * @throws IllegalArgumentException if the category does not belong to the categorization
  158.      * @throws NullPointerException if an input is null
  159.      */
  160.     public final void putDemandVector(final Node origin, final Node destination, final Category category,
  161.         final FrequencyVector demand, final DurationVector timeVector, final Interpolation interpolation)
  162.     {
  163.         Throw.whenNull(origin, "Origin may not be null.");
  164.         Throw.whenNull(destination, "Destination may not be null.");
  165.         Throw.whenNull(category, "Category may not be null.");
  166.         Throw.whenNull(demand, "Demand data may not be null.");
  167.         Throw.whenNull(timeVector, "Time vector may not be null.");
  168.         Throw.whenNull(interpolation, "Interpolation may not be null.");
  169.         Throw.when(!this.origins.contains(origin), IllegalArgumentException.class,
  170.             "Origin '%s' is not part of the OD matrix.", origin);
  171.         Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
  172.             "Destination '%s' is not part of the OD matrix.", destination);
  173.         Throw.when(!this.categorization.equals(category.getCategorization()), IllegalArgumentException.class,
  174.             "Provided category %s does not belong to the categorization %s.", category, this.categorization);
  175.         ODEntry odEntry = new ODEntry(demand, timeVector, interpolation); // performs checks on vector length
  176.         this.demandData.get(origin).get(destination).put(category, odEntry);
  177.     }

  178.     /**
  179.      * @param origin origin
  180.      * @param destination destination
  181.      * @param category category
  182.      * @return demand data for given origin, destination and categorization, {@code null} if no data is given
  183.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  184.      * @throws IllegalArgumentException if the category does not belong to the categorization
  185.      * @throws NullPointerException if an input is null
  186.      */
  187.     public final FrequencyVector getDemandVector(final Node origin, final Node destination, final Category category)
  188.     {
  189.         ODEntry odEntry = getODEntry(origin, destination, category);
  190.         if (odEntry == null)
  191.         {
  192.             return null;
  193.         }
  194.         return odEntry.getDemandVector();
  195.     }

  196.     /**
  197.      * @param origin origin
  198.      * @param destination destination
  199.      * @param category category
  200.      * @return interpolation for given origin, destination and categorization, {@code null} if no data is given
  201.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  202.      * @throws IllegalArgumentException if the category does not belong to the categorization
  203.      * @throws NullPointerException if an input is null
  204.      */
  205.     public final DurationVector getTimeVector(final Node origin, final Node destination, final Category category)
  206.     {
  207.         ODEntry odEntry = getODEntry(origin, destination, category);
  208.         if (odEntry == null)
  209.         {
  210.             return null;
  211.         }
  212.         return odEntry.getTimeVector();
  213.     }

  214.     /**
  215.      * @param origin origin
  216.      * @param destination destination
  217.      * @param category category
  218.      * @return interpolation for given origin, destination and categorization, {@code null} if no data is given
  219.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  220.      * @throws IllegalArgumentException if the category does not belong to the categorization
  221.      * @throws NullPointerException if an input is null
  222.      */
  223.     public final Interpolation getInterpolation(final Node origin, final Node destination, final Category category)
  224.     {
  225.         ODEntry odEntry = getODEntry(origin, destination, category);
  226.         if (odEntry == null)
  227.         {
  228.             return null;
  229.         }
  230.         return odEntry.getInterpolation();
  231.     }

  232.     /**
  233.      * Returns the demand at given time. If given time is before the first time slice or after the last time slice, 0 demand is
  234.      * returned.
  235.      * @param origin origin
  236.      * @param destination destination
  237.      * @param category category
  238.      * @param time time
  239.      * @return demand for given origin, destination and categorization, at given time
  240.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  241.      * @throws IllegalArgumentException if the category does not belong to the categorization
  242.      * @throws NullPointerException if an input is null
  243.      */
  244.     public final Frequency
  245.         getDemand(final Node origin, final Node destination, final Category category, final Duration time)
  246.     {
  247.         Throw.whenNull(time, "Time may not be null.");
  248.         ODEntry odEntry = getODEntry(origin, destination, category);
  249.         if (odEntry == null)
  250.         {
  251.             return new Frequency(0.0, FrequencyUnit.PER_HOUR); // Frequency.ZERO give "Hz" which is not nice for flow
  252.         }
  253.         return odEntry.getDemand(time);
  254.     }

  255.     /**
  256.      * @param origin origin
  257.      * @param destination destination
  258.      * @param category category
  259.      * @return OD entry for given origin, destination and categorization.
  260.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  261.      * @throws IllegalArgumentException if the category does not belong to the categorization
  262.      * @throws NullPointerException if an input is null
  263.      */
  264.     private ODEntry getODEntry(final Node origin, final Node destination, final Category category)
  265.     {
  266.         Throw.whenNull(origin, "Origin may not be null.");
  267.         Throw.whenNull(destination, "Destination may not be null.");
  268.         Throw.whenNull(category, "Category may not be null.");
  269.         Throw.when(!this.origins.contains(origin), IllegalArgumentException.class,
  270.             "Origin '%s' is not part of the OD matrix", origin);
  271.         Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
  272.             "Destination '%s' is not part of the OD matrix.", destination);
  273.         Throw.when(!this.categorization.equals(category.getCategorization()), IllegalArgumentException.class,
  274.             "Provided category %s does not belong to the categorization %s.", category, this.categorization);
  275.         return this.demandData.get(origin).get(destination).get(category);
  276.     }
  277.    
  278.     /**
  279.      * @param origin origin
  280.      * @param destination destination
  281.      * @param category category
  282.      * @return whether there is data for the specified origin, destination and category
  283.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  284.      * @throws IllegalArgumentException if the category does not belong to the categorization
  285.      * @throws NullPointerException if an input is null
  286.      */
  287.     public final boolean contains(final Node origin, final Node destination, final Category category)
  288.     {
  289.         return getODEntry(origin, destination, category) != null;
  290.     }
  291.    
  292.     /**
  293.      * Returns the categories specified for given origin-destination combination.
  294.      * @param origin origin
  295.      * @param destination destination
  296.      * @return categories specified for given origin-destination combination
  297.      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
  298.      * @throws NullPointerException if an input is null
  299.      */
  300.     public final Set<Category> getCategories(final Node origin, final Node destination)
  301.     {
  302.         Throw.whenNull(origin, "Origin may not be null.");
  303.         Throw.whenNull(destination, "Destination may not be null.");
  304.         Throw.when(!this.origins.contains(origin), IllegalArgumentException.class,
  305.             "Origin '%s' is not part of the OD matrix", origin);
  306.         Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
  307.             "Destination '%s' is not part of the OD matrix.", destination);
  308.         return new HashSet<>(this.demandData.get(origin).get(destination).keySet());
  309.     }

  310.     /** {@inheritDoc} */
  311.     @SuppressWarnings("checkstyle:designforextension")
  312.     public String toString()
  313.     {
  314.         return "ODMatrix [" + this.origins.size() + " origins, " + this.destinations.size() + " destinations, "
  315.             + this.categorization + " ]";
  316.     }

  317.     /**
  318.      * Prints the complete OD matrix with each demand data on a single line.
  319.      */
  320.     public final void print()
  321.     {
  322.         int originLength = 0;
  323.         for (Node origin : this.origins)
  324.         {
  325.             originLength = originLength >= origin.getId().length() ? originLength : origin.getId().length();
  326.         }
  327.         int destinLength = 0;
  328.         for (Node destination : this.destinations)
  329.         {
  330.             destinLength = destinLength >= destination.getId().length() ? destinLength : destination.getId().length();
  331.         }
  332.         String format = "%-" + originLength + "s -> %-" + destinLength + "s | ";
  333.         for (Node origin : this.origins)
  334.         {
  335.             Map<Node, Map<Category, ODEntry>> destinationMap = this.demandData.get(origin);
  336.             for (Node destination : this.destinations)
  337.             {
  338.                 Map<Category, ODEntry> categoryMap = destinationMap.get(destination);
  339.                 if (categoryMap.isEmpty())
  340.                 {
  341.                     System.out.println(String.format(format, origin.getId(), destination.getId()) + "-no data-");
  342.                 }
  343.                 else
  344.                 {
  345.                     for (Category category : categoryMap.keySet())
  346.                     {
  347.                         System.out.println(String.format(format, origin.getId(), destination.getId()) + category + " | "
  348.                             + categoryMap.get(category).getDemandVector());
  349.                     }
  350.                 }
  351.             }
  352.         }
  353.     }

  354.     /** {@inheritDoc} */
  355.     @Override
  356.     public final int hashCode()
  357.     {
  358.         final int prime = 31;
  359.         int result = 1;
  360.         result = prime * result + ((this.categorization == null) ? 0 : this.categorization.hashCode());
  361.         result = prime * result + ((this.demandData == null) ? 0 : this.demandData.hashCode());
  362.         result = prime * result + ((this.destinations == null) ? 0 : this.destinations.hashCode());
  363.         result = prime * result + ((this.globalInterpolation == null) ? 0 : this.globalInterpolation.hashCode());
  364.         result = prime * result + ((this.globalTimeVector == null) ? 0 : this.globalTimeVector.hashCode());
  365.         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
  366.         result = prime * result + ((this.origins == null) ? 0 : this.origins.hashCode());
  367.         return result;
  368.     }

  369.     /** {@inheritDoc} */
  370.     @Override
  371.     public final boolean equals(final Object obj)
  372.     {
  373.         if (this == obj)
  374.         {
  375.             return true;
  376.         }
  377.         if (obj == null)
  378.         {
  379.             return false;
  380.         }
  381.         if (getClass() != obj.getClass())
  382.         {
  383.             return false;
  384.         }
  385.         ODMatrix other = (ODMatrix) obj;
  386.         if (this.categorization == null)
  387.         {
  388.             if (other.categorization != null)
  389.             {
  390.                 return false;
  391.             }
  392.         }
  393.         else if (!this.categorization.equals(other.categorization))
  394.         {
  395.             return false;
  396.         }
  397.         if (this.demandData == null)
  398.         {
  399.             if (other.demandData != null)
  400.             {
  401.                 return false;
  402.             }
  403.         }
  404.         else if (!this.demandData.equals(other.demandData))
  405.         {
  406.             return false;
  407.         }
  408.         if (this.destinations == null)
  409.         {
  410.             if (other.destinations != null)
  411.             {
  412.                 return false;
  413.             }
  414.         }
  415.         else if (!this.destinations.equals(other.destinations))
  416.         {
  417.             return false;
  418.         }
  419.         if (this.globalInterpolation != other.globalInterpolation)
  420.         {
  421.             return false;
  422.         }
  423.         if (this.globalTimeVector == null)
  424.         {
  425.             if (other.globalTimeVector != null)
  426.             {
  427.                 return false;
  428.             }
  429.         }
  430.         else if (!this.globalTimeVector.equals(other.globalTimeVector))
  431.         {
  432.             return false;
  433.         }
  434.         if (this.id == null)
  435.         {
  436.             if (other.id != null)
  437.             {
  438.                 return false;
  439.             }
  440.         }
  441.         else if (!this.id.equals(other.id))
  442.         {
  443.             return false;
  444.         }
  445.         if (this.origins == null)
  446.         {
  447.             if (other.origins != null)
  448.             {
  449.                 return false;
  450.             }
  451.         }
  452.         else if (!this.origins.equals(other.origins))
  453.         {
  454.             return false;
  455.         }
  456.         return true;
  457.     }

  458.     // TODO remove this method as soon as there is a JUNIT test
  459.     public static void main(final String[] args) throws ValueException, NetworkException
  460.     {

  461.         int aa = 10;
  462.         System.out.println(aa);
  463.         aa = aa + (aa >> 1);
  464.         System.out.println(aa);
  465.         aa = aa + (aa >> 1);
  466.         System.out.println(aa);
  467.         aa = aa + (aa >> 1);
  468.         System.out.println(aa);
  469.         aa = aa + (aa >> 1);
  470.         System.out.println(aa);

  471.         OTSNetwork net = new OTSNetwork("test");
  472.         OTSPoint3D point = new OTSPoint3D(0, 0, 0);
  473.         Node a = new OTSNode(net, "A", point);
  474.         Node b = new OTSNode(net, "Barendrecht", point);
  475.         Node c = new OTSNode(net, "C", point);
  476.         Node d = new OTSNode(net, "Delft", point);
  477.         Node e = new OTSNode(net, "E", point);
  478.         List<Node> origins = new ArrayList<>();
  479.         origins.add(a);
  480.         origins.add(b);
  481.         origins.add(c);
  482.         List<Node> destinations = new ArrayList<>();
  483.         destinations.add(a);
  484.         destinations.add(c);
  485.         destinations.add(d);
  486.         Categorization categorization = new Categorization("test", Route.class, String.class);
  487.         Route ac1 = new Route("AC1");
  488.         Route ac2 = new Route("AC2");
  489.         Route ad1 = new Route("AD1");
  490.         Route bc1 = new Route("BC1");
  491.         Route bc2 = new Route("BC2");
  492.         Route bd1 = new Route("BD1");

  493.         DurationVector timeVector = new DurationVector(new double[] {0, 1200, 3600}, TimeUnit.SECOND, StorageType.DENSE);
  494.         ODMatrix odMatrix = new ODMatrix("TestOD", origins, destinations, categorization, timeVector, Interpolation.LINEAR);

  495.         Category category = new Category(categorization, ac1, "car");
  496.         odMatrix.putDemandVector(a, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  497.             StorageType.DENSE));
  498.         category = new Category(categorization, ac2, "car");
  499.         odMatrix.putDemandVector(a, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  500.             StorageType.DENSE));
  501.         category = new Category(categorization, ad1, "car");
  502.         odMatrix.putDemandVector(a, d, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  503.             StorageType.DENSE));
  504.         category = new Category(categorization, ac1, "car");
  505.         odMatrix.putDemandVector(a, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  506.             StorageType.DENSE));
  507.         category = new Category(categorization, ac2, "truck");
  508.         odMatrix.putDemandVector(a, c, category, new FrequencyVector(new double[] {100, 200, 500}, FrequencyUnit.PER_HOUR,
  509.             StorageType.DENSE));
  510.         category = new Category(categorization, ad1, "truck");
  511.         odMatrix.putDemandVector(a, d, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  512.             StorageType.DENSE));
  513.         category = new Category(categorization, bc1, "truck");
  514.         odMatrix.putDemandVector(b, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  515.             StorageType.DENSE));
  516.         category = new Category(categorization, bc2, "truck");
  517.         odMatrix.putDemandVector(b, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  518.             StorageType.DENSE));
  519.         category = new Category(categorization, bd1, "car");
  520.         odMatrix.putDemandVector(b, d, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  521.             StorageType.DENSE));
  522.         category = new Category(categorization, bc1, "car");
  523.         odMatrix.putDemandVector(b, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  524.             StorageType.DENSE));
  525.         category = new Category(categorization, bc2, "car");
  526.         odMatrix.putDemandVector(b, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  527.             StorageType.DENSE));
  528.         category = new Category(categorization, bd1, "truck");
  529.         odMatrix.putDemandVector(b, d, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
  530.             StorageType.DENSE));

  531.         odMatrix.print();
  532.         System.out.println(odMatrix);

  533.         category = new Category(categorization, ac2, "truck");
  534.         for (double t = -100; t <= 3700; t += 100)
  535.         {
  536.             Duration time = new Duration(t, TimeUnit.SECOND);
  537.             System.out.println("@ t = " + time + ", q = " + odMatrix.getDemand(a, c, category, time));
  538.         }

  539.         System.out.println("For OD       that does not exist; q = " + odMatrix.getDemand(c, a, category, Duration.ZERO));
  540.         category = new Category(categorization, ac2, "does not exist");
  541.         System.out.println("For category that does not exist; q = " + odMatrix.getDemand(a, c, category, Duration.ZERO));

  542.     }

  543.     /**
  544.      * An ODEntry contains a demand vector, and optionally a time vector and interpolation method that may differ from the
  545.      * global time vector or interpolation method.
  546.      * <p>
  547.      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  548.      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
  549.      * <p>
  550.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 16, 2016 <br>
  551.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  552.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  553.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  554.      */
  555.     private class ODEntry
  556.     {

  557.         /** Demand vector. */
  558.         private final FrequencyVector demandVector;

  559.         /** Time vector, may be null. */
  560.         private final DurationVector timeVector;

  561.         /** Interpolation, may be null. */
  562.         private final Interpolation interpolation;

  563.         /**
  564.          * @param demandVector demand vector
  565.          * @param timeVector time vector
  566.          * @param interpolation interpolation
  567.          * @throws IllegalArgumentException if the demand data has a different length than time data
  568.          */
  569.         ODEntry(final FrequencyVector demandVector, final DurationVector timeVector, final Interpolation interpolation)
  570.         {
  571.             Throw.when(demandVector.size() != timeVector.size(), IllegalArgumentException.class,
  572.                 "Demand data has different length than time vector.");
  573.             this.demandVector = demandVector;
  574.             this.timeVector = timeVector;
  575.             this.interpolation = interpolation;
  576.         }

  577.         /**
  578.          * Returns the demand at given time. If given time is before the first time slice or after the last time slice, 0 demand
  579.          * is returned.
  580.          * @param time time of demand requested
  581.          * @return demand at given time
  582.          */
  583.         public final Frequency getDemand(final Duration time)
  584.         {
  585.             try
  586.             {
  587.                 // empty data or before start or after end, return 0
  588.                 if (this.timeVector.size() == 0 || time.lt(this.timeVector.get(0))
  589.                     || time.ge(this.timeVector.get(this.timeVector.size() - 1)))
  590.                 {
  591.                     return new Frequency(0.0, FrequencyUnit.PER_HOUR); // Frequency.ZERO give "Hz" which is not nice for flow
  592.                 }
  593.                 // interpolate
  594.                 for (int i = 0; i < this.timeVector.size() - 1; i++)
  595.                 {
  596.                     if (this.timeVector.get(i + 1).ge(time))
  597.                     {
  598.                         return this.interpolation.interpolate(this.demandVector.get(i), this.timeVector.get(i),
  599.                             this.demandVector.get(i + 1), this.timeVector.get(i + 1), time);
  600.                     }
  601.                 }
  602.             }
  603.             catch (ValueException ve)
  604.             {
  605.                 // should not happen, vector lengths are checked when given is input
  606.                 throw new RuntimeException("Index out of bounds.", ve);
  607.             }
  608.             // should not happen
  609.             throw new RuntimeException("Demand interpolation failed.");
  610.         }

  611.         /**
  612.          * @return demandVector
  613.          */
  614.         final FrequencyVector getDemandVector()
  615.         {
  616.             return this.demandVector;
  617.         }

  618.         /**
  619.          * @return timeVector
  620.          */
  621.         final DurationVector getTimeVector()
  622.         {
  623.             return this.timeVector;
  624.         }

  625.         /**
  626.          * @return interpolation
  627.          */
  628.         final Interpolation getInterpolation()
  629.         {
  630.             return this.interpolation;
  631.         }

  632.         /** {@inheritDoc} */
  633.         @Override
  634.         public final int hashCode()
  635.         {
  636.             final int prime = 31;
  637.             int result = 1;
  638.             result = prime * result + getOuterType().hashCode();
  639.             result = prime * result + ((this.demandVector == null) ? 0 : this.demandVector.hashCode());
  640.             result = prime * result + ((this.interpolation == null) ? 0 : this.interpolation.hashCode());
  641.             result = prime * result + ((this.timeVector == null) ? 0 : this.timeVector.hashCode());
  642.             return result;
  643.         }

  644.         /** {@inheritDoc} */
  645.         @Override
  646.         public final boolean equals(final Object obj)
  647.         {
  648.             if (this == obj)
  649.             {
  650.                 return true;
  651.             }
  652.             if (obj == null)
  653.             {
  654.                 return false;
  655.             }
  656.             if (getClass() != obj.getClass())
  657.             {
  658.                 return false;
  659.             }
  660.             ODEntry other = (ODEntry) obj;
  661.             if (!getOuterType().equals(other.getOuterType()))
  662.             {
  663.                 return false;
  664.             }
  665.             if (this.demandVector == null)
  666.             {
  667.                 if (other.demandVector != null)
  668.                 {
  669.                     return false;
  670.                 }
  671.             }
  672.             else if (!this.demandVector.equals(other.demandVector))
  673.             {
  674.                 return false;
  675.             }
  676.             if (this.interpolation != other.interpolation)
  677.             {
  678.                 return false;
  679.             }
  680.             if (this.timeVector == null)
  681.             {
  682.                 if (other.timeVector != null)
  683.                 {
  684.                     return false;
  685.                 }
  686.             }
  687.             else if (!this.timeVector.equals(other.timeVector))
  688.             {
  689.                 return false;
  690.             }
  691.             return true;
  692.         }

  693.         /**
  694.          * Accessor for hashcode and equals.
  695.          * @return encompassing OD matrix
  696.          */
  697.         private ODMatrix getOuterType()
  698.         {
  699.             return ODMatrix.this;
  700.         }

  701.         /** {@inheritDoc} */
  702.         @Override
  703.         public String toString()
  704.         {
  705.             return "ODEntry [demandVector=" + this.demandVector + ", timeVector=" + this.timeVector + ", interpolation="
  706.                     + this.interpolation + "]";
  707.         }

  708.     }

  709. }