View Javadoc
1   package org.opentrafficsim.road.network.factory.xml.demand;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.LinkedHashSet;
6   import java.util.List;
7   
8   import org.djunits.unit.FrequencyUnit;
9   import org.djunits.unit.TimeUnit;
10  import org.djunits.value.StorageType;
11  import org.djunits.value.vdouble.scalar.Time;
12  import org.djunits.value.vdouble.vector.FrequencyVector;
13  import org.djunits.value.vdouble.vector.TimeVector;
14  import org.opentrafficsim.core.gtu.Try;
15  import org.opentrafficsim.core.network.factory.xml.units.DurationUnits;
16  import org.opentrafficsim.core.network.factory.xml.units.TimeUnits;
17  import org.opentrafficsim.road.gtu.strategical.od.Category;
18  import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
19  import org.opentrafficsim.road.network.factory.xml.XMLParser;
20  import org.opentrafficsim.road.network.factory.xml.XmlParserException;
21  import org.w3c.dom.NamedNodeMap;
22  import org.w3c.dom.Node;
23  import org.w3c.dom.NodeList;
24  
25  import nl.tudelft.simulation.language.Throw;
26  
27  /**
28   * Demand.
29   * <p>
30   * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
31   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
32   * <p>
33   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 25 mei 2018 <br>
34   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
35   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
36   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
37   */
38  
39  public class DemandTag implements Serializable
40  {
41  
42      /** */
43      private static final long serialVersionUID = 20180525L;
44  
45      /** Unit for storage. */
46      private final static FrequencyUnit UNIT = FrequencyUnit.PER_HOUR;
47  
48      /** Origin. */
49      org.opentrafficsim.core.network.Node origin;
50  
51      /** Destination. */
52      org.opentrafficsim.core.network.Node destination;
53  
54      /** Interpolation. */
55      Interpolation interpolation;
56  
57      /** Category. */
58      Category category;
59  
60      /** Category name. */
61      String categoryName;
62  
63      /** Demand type. */
64      DemandType demandType = null;
65  
66      /** Time vector. */
67      TimeVector timeVector;
68  
69      /** Demand vector. */
70      FrequencyVector demandVector;
71  
72      /** Fraction. */
73      Double factor;
74  
75      /** Fractions. */
76      double[] factors;
77  
78      /**
79       * Parse demand nodes.
80       * @param nodeList NodeList; node list
81       * @param parser XmlOdParser; parser
82       * @throws XmlParserException if category cannot be parsed
83       */
84      static void parse(final NodeList nodeList, final XmlOdParser parser) throws XmlParserException
85      {
86          for (Node node : XMLParser.getNodesSorted(nodeList, "DEMAND", "ORIGIN", "DESTINATION", "CATEGORY"))
87          {
88              NamedNodeMap attributes = node.getAttributes();
89              DemandTag tag = new DemandTag();
90  
91              Node originNode = attributes.getNamedItem("ORIGIN");
92              Throw.when(originNode == null, XmlParserException.class, "Missing ORIGIN attribute in DEMAND tag.");
93              String originId = originNode.getNodeValue().trim();
94              tag.origin = parser.network.getNode(originId);
95              Throw.when(tag.origin == null, XmlParserException.class, "Origin %s is not available.", originId);
96  
97              Node destinationNode = attributes.getNamedItem("DESTINATION");
98              Throw.when(destinationNode == null, XmlParserException.class, "Missing DESTINATION attribute in DEMAND tag.");
99              String destinationId = destinationNode.getNodeValue().trim();
100             tag.destination = parser.network.getNode(destinationId);
101             Throw.when(tag.destination == null, XmlParserException.class, "Destination %s is not available.", destinationId);
102 
103             Node interpolationNode = attributes.getNamedItem("INTERPOLATION");
104             if (interpolationNode != null)
105             {
106                 String interpolation = interpolationNode.getNodeValue().trim();
107                 try
108                 {
109                     tag.interpolation = Interpolation.valueOf(interpolation);
110                 }
111                 catch (IllegalArgumentException exception)
112                 {
113                     throw new XmlParserException("INTERPOLATION " + interpolation + " does not exist.", exception);
114                 }
115             }
116 
117             Node factorNode = attributes.getNamedItem("FACTOR");
118             if (factorNode != null)
119             {
120                 tag.factor = parseFactor(factorNode.getNodeValue().trim());
121             }
122 
123             Node categoryNode = attributes.getNamedItem("CATEGORY");
124             if (categoryNode != null)
125             {
126                 String categoryName = categoryNode.getNodeValue().trim();
127                 Throw.when(!parser.categories.containsKey(categoryName), XmlParserException.class,
128                         "Category %s is not available.", categoryName);
129                 tag.category = parser.categories.get(categoryName).getCategory(parser.categorization);
130                 tag.factor = XmlOdParser.nullMultiply(tag.factor, parser.categories.get(categoryName).factor);
131             }
132 
133             NodeList childList = node.getChildNodes();
134             List<Double> timeList = new ArrayList<>();
135             List<Double> valueList = new ArrayList<>();
136             List<Node> demandNodes = XMLParser.getNodes(childList, "LEVEL");
137             Throw.when(categoryNode == null && demandNodes.isEmpty(), XmlParserException.class,
138                     "DEMAND without CATEGORY attribute should contain demand data.");
139             if (demandNodes.size() == 0)
140             {
141                 tag.demandType = DemandType.FACTOR;
142             }
143             for (Node level : demandNodes)
144             {
145                 if (tag.demandType == null)
146                 {
147                     tag.demandType = DemandType.fromLevelNode(level);
148                     Throw.when(
149                             categoryNode == null && !tag.demandType.equals(DemandType.FREQUENCIES)
150                                     && !tag.demandType.equals(DemandType.TIMED_FREQUENCIES),
151                             XmlParserException.class,
152                             "DEMAND without CATEGORY attribute should contain non-factoral demand data.");
153                 }
154                 NamedNodeMap levelAttributes = level.getAttributes();
155                 if (tag.demandType.equals(DemandType.TIMED_FACTORS) || tag.demandType.equals(DemandType.TIMED_FREQUENCIES))
156                 {
157                     Node timeNode = levelAttributes.getNamedItem("TIME");
158                     Throw.when(timeNode == null, XmlParserException.class, "A LEVEL tag is missing attribute TIME.");
159                     String timeString = timeNode.getNodeValue().trim();
160                     Time time = Try.assign(() -> TimeUnits.parseTime(timeString), XmlParserException.class,
161                             "Unable to parse %s as time.", timeString);
162                     timeList.add(time.si);
163                 }
164                 Node valueNode = levelAttributes.getNamedItem("VALUE");
165                 Throw.when(valueNode == null, XmlParserException.class, "A LEVEL tag is missing attribute VALUE.");
166                 if (tag.demandType.equals(DemandType.TIMED_FACTORS) || tag.demandType.equals(DemandType.FACTORS))
167                 {
168                     valueList.add(parseFactor(valueNode.getNodeValue().trim()));
169                 }
170                 if (tag.demandType.equals(DemandType.TIMED_FREQUENCIES) || tag.demandType.equals(DemandType.FREQUENCIES))
171                 {
172                     String valueString = valueNode.getNodeValue().trim().toLowerCase();
173                     valueList.add(Try.assign(() -> DurationUnits.parseFrequency(valueString.replace("veh", "")).getInUnit(UNIT),
174                             "Unable to parse %s as frequency.", valueString));
175                 }
176             }
177             // time vector
178             if (tag.demandType.equals(DemandType.TIMED_FACTORS) || tag.demandType.equals(DemandType.TIMED_FREQUENCIES))
179             {
180                 tag.timeVector = Try.assign(
181                         () -> new TimeVector(timeList.stream().mapToDouble(d -> d).toArray(), TimeUnit.BASE, StorageType.DENSE),
182                         "Unexpected exception while converting list of time values to an array.");
183             }
184             // factor or demand vector
185             if (tag.demandType.equals(DemandType.TIMED_FACTORS) || tag.demandType.equals(DemandType.FACTORS))
186             {
187                 tag.factors = valueList.stream().mapToDouble(d -> d).toArray();
188             }
189             else if (tag.demandType.equals(DemandType.TIMED_FREQUENCIES) || tag.demandType.equals(DemandType.FREQUENCIES))
190             {
191                 tag.demandVector = Try.assign(
192                         () -> new FrequencyVector(valueList.stream().mapToDouble(d -> d).toArray(), UNIT, StorageType.DENSE),
193                         "Unexpected exception while converting list of time values to an array.");
194             }
195 
196             parser.demand.getValue(() -> new LinkedHashSet<>(), tag.origin, tag.destination).add(tag);
197         }
198     }
199 
200     /**
201      * Parses a String of percentage or value to a double.
202      * @param value String; value
203      * @return double
204      * @throws XmlParserException if factor is not in range from 0 to 1
205      */
206     public static double parseFactor(final String value) throws XmlParserException
207     {
208         double f;
209         if (value.endsWith("%"))
210         {
211             f = Double.parseDouble(value.substring(0, value.length() - 1).trim()) / 100.0;
212         }
213         else
214         {
215             f = Double.parseDouble(value);
216         }
217         Throw.when(f < 0.0, XmlParserException.class, "Factor %d is not positive.", f);
218         return f;
219     }
220 
221     /**
222      * Demand type. Demand may be defined in several tags. For instance, 1 tag may define time and demand, while other tags only
223      * specify a factor of this demand applicable to a specific category.
224      * <p>
225      * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
226      * <br>
227      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
228      * <p>
229      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 25 mei 2018 <br>
230      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
231      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
232      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
233      */
234     public enum DemandType
235     {
236         /** Demand given in other tag to which a factor applies. */
237         FACTOR,
238 
239         /** Demand given in other tag to which a factors apply. */
240         FACTORS,
241 
242         /** Timed factors that apply to demand in another tag. */
243         TIMED_FACTORS,
244 
245         /** Frequencies that apply to the global time vector. */
246         FREQUENCIES,
247 
248         /** Timed frequencies. */
249         TIMED_FREQUENCIES;
250 
251         /**
252          * Returns the demand type based on a LEVEL tag. This does not work on FACTOR, as no LEVEL tag should be defined then.
253          * @param level level of the node
254          * @return demand type
255          * @throws XmlParserException if the VALUE attribute is missing
256          */
257         public static DemandType fromLevelNode(final Node level) throws XmlParserException
258         {
259             NamedNodeMap attributes = level.getAttributes();
260             Node timeNode = attributes.getNamedItem("TIME");
261             Node valueNode = attributes.getNamedItem("VALUE");
262             Throw.when(valueNode == null, XmlParserException.class, "LEVEL tag in DEMAND is missing attribute VALUE.");
263             boolean frequency = valueNode.getNodeValue().trim().toLowerCase().contains("veh");
264             if (timeNode != null)
265             {
266                 if (frequency)
267                 {
268                     return TIMED_FREQUENCIES;
269                 }
270                 return TIMED_FACTORS;
271             }
272             if (frequency)
273             {
274                 return FREQUENCIES;
275             }
276             return FACTORS;
277         }
278 
279     }
280 
281 }