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