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