View Javadoc
1   package org.opentrafficsim.road.network.factory.xml.demand;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.io.Serializable;
6   import java.net.URL;
7   import java.util.ArrayList;
8   import java.util.HashMap;
9   import java.util.Iterator;
10  import java.util.LinkedHashMap;
11  import java.util.LinkedHashSet;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.SortedSet;
16  import java.util.TreeSet;
17  
18  import javax.xml.parsers.DocumentBuilder;
19  import javax.xml.parsers.DocumentBuilderFactory;
20  import javax.xml.parsers.ParserConfigurationException;
21  
22  import org.djunits.unit.TimeUnit;
23  import org.djunits.value.StorageType;
24  import org.djunits.value.vdouble.scalar.Time;
25  import org.djunits.value.vdouble.vector.FrequencyVector;
26  import org.djunits.value.vdouble.vector.TimeVector;
27  import org.djutils.exceptions.Throw;
28  import org.djutils.exceptions.Try;
29  import org.opentrafficsim.base.parameters.ParameterException;
30  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
31  import org.opentrafficsim.core.gtu.GTUType;
32  import org.opentrafficsim.core.gtu.NestedCache;
33  import org.opentrafficsim.core.network.factory.xml.units.TimeUnits;
34  import org.opentrafficsim.road.gtu.generator.od.ODApplier;
35  import org.opentrafficsim.road.gtu.generator.od.ODOptions;
36  import org.opentrafficsim.road.gtu.strategical.od.Categorization;
37  import org.opentrafficsim.road.gtu.strategical.od.Category;
38  import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
39  import org.opentrafficsim.road.gtu.strategical.od.ODMatrix;
40  import org.opentrafficsim.road.network.OTSRoadNetwork;
41  import org.opentrafficsim.road.network.factory.xml.XmlParserException;
42  import org.w3c.dom.Document;
43  import org.w3c.dom.NamedNodeMap;
44  import org.w3c.dom.Node;
45  import org.w3c.dom.NodeList;
46  import org.xml.sax.SAXException;
47  
48  import nl.tudelft.simulation.dsol.SimRuntimeException;
49  
50  /**
51   * OD parser.
52   * <p>
53   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
54   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
55   * <p>
56   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 25 mei 2018 <br>
57   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
58   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
59   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
60   */
61  public class XmlOdParser implements Serializable
62  {
63  
64      /** */
65      private static final long serialVersionUID = 20180525L;
66  
67      /** Simulator. */
68      private final OTSSimulatorInterface simulator;
69  
70      /** Network. */
71      final OTSRoadNetwork network;
72  
73      /** GTU types. */
74      private final Map<String, GTUType> gtuTypes = new HashMap<>();
75  
76      /** Categorization. */
77      Categorization categorization;
78  
79      /** Categories. */
80      Map<String, CategoryTag> categories = new LinkedHashMap<>();
81  
82      /** Global time vector. */
83      TimeVector globalTime;
84  
85      /** Global interpolation. */
86      Interpolation globalInterpolation;
87  
88      /** Demand. */
89      NestedCache<Set<DemandTag>> demand =
90              new NestedCache<>(org.opentrafficsim.core.network.Node.class, org.opentrafficsim.core.network.Node.class);
91  
92      /**
93       * Constructor.
94       * @param simulator OTSSimulatorInterface; simulator
95       * @param network OTSRoadNetwork; network
96       * @param gtuTypes Set&lt;GTUType&gt;; set of GTU types
97       */
98      public XmlOdParser(final OTSSimulatorInterface simulator, final OTSRoadNetwork network, final Set<GTUType> gtuTypes)
99      {
100         Throw.whenNull(simulator, "Simulator should not be null.");
101         Throw.whenNull(network, "Network should not be null.");
102         this.simulator = simulator;
103         this.network = network;
104         for (GTUType gtuType : gtuTypes)
105         {
106             this.gtuTypes.put(gtuType.getId(), gtuType);
107         }
108     }
109 
110     /**
111      * Returns the GTU type for the given id.
112      * @param id String; id
113      * @return GTU type for the given id
114      * @throws XmlParserException if the GTU type is not available
115      */
116     public GTUType getGTUType(final String id) throws XmlParserException
117     {
118         Throw.when(!this.gtuTypes.containsKey(id), XmlParserException.class, "GTU type %s is not available.", id);
119         return this.gtuTypes.get(id);
120     }
121 
122     /**
123      * Applies demand from URL.
124      * @param url URL; URL to file
125      * @throws XmlParserException if URL cannot be parsed
126      */
127     public final void apply(final URL url) throws XmlParserException
128     {
129         apply(url, new ODOptions());
130     }
131 
132     /**
133      * Applies demand from URL using OD options.
134      * @param url URL; URL to file
135      * @param odOptions ODOptions; OD options
136      * @throws XmlParserException if URL cannot be parsed
137      */
138     public void apply(final URL url, final ODOptions odOptions) throws XmlParserException
139     {
140         try
141         {
142             apply(url.openStream(), odOptions);
143         }
144         catch (IOException exception)
145         {
146             throw new XmlParserException(exception);
147         }
148     }
149 
150     /**
151      * Applies demand from stream.
152      * @param stream InputStream; stream
153      * @throws XmlParserException if URL cannot be parsed
154      */
155     public final void apply(final InputStream stream) throws XmlParserException
156     {
157         apply(stream, new ODOptions());
158     }
159 
160     /**
161      * Applies demand from stream using OD options.
162      * @param stream InputStream; stream
163      * @param odOptions ODOptions; OD options
164      * @throws XmlParserException if URL cannot be parsed
165      */
166     public final void apply(final InputStream stream, final ODOptions odOptions) throws XmlParserException
167     {
168         applyOD(build(stream), odOptions);
169     }
170 
171     /**
172      * Applies demand from xml node.
173      * @param xmlNode Node; node
174      * @throws XmlParserException if URL cannot be parsed
175      */
176     public final void apply(final Node xmlNode) throws XmlParserException
177     {
178         apply(xmlNode, new ODOptions());
179     }
180 
181     /**
182      * Applies demand from URL using OD options.
183      * @param xmlNode Node; node
184      * @param odOptions ODOptions; OD options
185      * @throws XmlParserException if URL cannot be parsed
186      */
187     public void apply(final Node xmlNode, final ODOptions odOptions) throws XmlParserException
188     {
189         applyOD(build(xmlNode), odOptions);
190     }
191 
192     /**
193      * Applies the OD to the network.
194      * @param od ODMatrix; OD matrix
195      * @param odOptions ODOptions; options
196      * @throws XmlParserException if the ODApplier fails
197      */
198     private void applyOD(final ODMatrix od, final ODOptions odOptions) throws XmlParserException
199     {
200         try
201         {
202             ODApplier.applyOD(this.network, od, this.simulator, odOptions);
203         }
204         catch (ParameterException | SimRuntimeException exception)
205         {
206             throw new XmlParserException(exception);
207         }
208     }
209 
210     /**
211      * Build demand from URL.
212      * @param url URL; URL to file
213      * @return ODMatrix; OD matrix
214      * @throws XmlParserException if URL cannot be parsed
215      */
216     public final ODMatrix build(final URL url) throws XmlParserException
217     {
218         try
219         {
220             return build(url.openStream());
221         }
222         catch (IOException exception)
223         {
224             throw new XmlParserException(exception);
225         }
226     }
227 
228     /**
229      * Build demand from stream.
230      * @param stream InputStream; stream
231      * @return ODMatrix; OD matrix
232      * @throws XmlParserException if stream cannot be parsed
233      */
234     public final ODMatrix build(final InputStream stream) throws XmlParserException
235     {
236         try
237         {
238             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
239             DocumentBuilder builder = factory.newDocumentBuilder();
240             Document document = builder.parse(stream);
241             return build(document.getDocumentElement());
242         }
243         catch (ParserConfigurationException | SAXException | IOException exception)
244         {
245             throw new XmlParserException(exception);
246         }
247     }
248 
249     /**
250      * Build demand from xml node.
251      * @param xmlNode Node; node
252      * @return ODMatrix; OD matrix
253      * @throws XmlParserException if stream cannot be parsed
254      */
255     public final ODMatrix build(final Node xmlNode) throws XmlParserException
256     {
257         Throw.when(!xmlNode.getNodeName().equals("OD"), XmlParserException.class,
258                 "OD should be parsed from a node OD, found %s instead.", xmlNode.getNodeName());
259 
260         // name
261         NamedNodeMap attributes = xmlNode.getAttributes();
262         Node nameNode = attributes.getNamedItem("NAME");
263         String name = nameNode == null ? "xml od" : nameNode.getNodeValue().trim();
264 
265         // global interpolation
266         Node globalInterpolationNode = attributes.getNamedItem("GLOBALINTERPOLATION");
267         if (globalInterpolationNode != null)
268         {
269             String globalInterpolationString = globalInterpolationNode.getNodeValue().trim();
270             try
271             {
272                 this.globalInterpolation = Interpolation.valueOf(globalInterpolationNode.getNodeValue().trim());
273             }
274             catch (IllegalArgumentException exception)
275             {
276                 throw new XmlParserException("INTERPOLATION " + globalInterpolationString + " does not exist.", exception);
277             }
278         }
279         else
280         {
281             this.globalInterpolation = Interpolation.LINEAR;
282         }
283 
284         // global factor
285         Node globalFractionNode = attributes.getNamedItem("GLOBALFACTOR");
286         Double globalFraction = null;
287         if (globalFractionNode != null)
288         {
289             String globalFractionString = globalFractionNode.getNodeValue().trim();
290             globalFraction = DemandTag.parseFactor(globalFractionString);
291         }
292 
293         // parse data
294         // TODO global time as optional
295         // TODO order of time values, and demand values later, is not guaranteed in xml, need a way to order them
296         NodeList odNodeList = xmlNode.getChildNodes();
297         List<Node> nodes = XMLParser.getNodes(odNodeList, "GLOBALTIME");
298         Throw.when(nodes.size() > 1, XmlParserException.class, "Multiple GLOBALTIME tags, only 1 is allowed.");
299         SortedSet<Time> timeSet = new TreeSet<>();
300         if (!nodes.isEmpty())
301         {
302             for (Node timeNode : XMLParser.getNodes(nodes.get(0).getChildNodes(), "TIME"))
303             {
304                 NamedNodeMap timeAttributes = timeNode.getAttributes();
305                 Node valueNode = timeAttributes.getNamedItem("VALUE");
306                 Throw.when(valueNode == null, XmlParserException.class, "LEVEL tag is missing VALUE attribute.");
307                 timeSet.add(Try.assign(() -> TimeUnits.parseTime(valueNode.getNodeValue()), XmlParserException.class,
308                         "Unable to parse time %s.", valueNode.getNodeValue()));
309             }
310         }
311         double[] timeArray = new double[timeSet.size()];
312         Iterator<Time> it = timeSet.iterator();
313         for (int i = 0; i < timeSet.size(); i++)
314         {
315             timeArray[i] = it.next().si;
316         }
317         this.globalTime = Try.assign(() -> new TimeVector(timeArray, TimeUnit.BASE, StorageType.DENSE),
318                 XmlParserException.class, "Unexpected exception while creating global time vector.");
319         CategoryTag.parse(odNodeList, this);
320         DemandTag.parse(odNodeList, this);
321 
322         // create OD matrix
323         List<org.opentrafficsim.core.network.Node> origins = new ArrayList<>();
324         List<org.opentrafficsim.core.network.Node> destinations = new ArrayList<>();
325         for (Object oKey : this.demand.getKeys())
326         {
327             origins.add((org.opentrafficsim.core.network.Node) oKey);
328             for (Object dKey : this.demand.getChild(oKey).getKeys())
329             {
330                 if (!destinations.contains(dKey))
331                 {
332                     destinations.add((org.opentrafficsim.core.network.Node) dKey);
333                 }
334             }
335         }
336         ODMatrix odMatrix =
337                 new ODMatrix(name, origins, destinations, this.categorization, this.globalTime, this.globalInterpolation);
338 
339         // add demand
340         for (org.opentrafficsim.core.network.Node origin : origins)
341         {
342             for (org.opentrafficsim.core.network.Node destination : destinations)
343             {
344                 Set<DemandTag> set = this.demand.getValue(() -> new LinkedHashSet<>(), origin, destination);
345                 if (!set.isEmpty())
346                 {
347                     // add demand
348                     DemandTag main = null;
349                     if (!this.categorization.equals(Categorization.UNCATEGORIZED))
350                     {
351                         for (DemandTag tag : set)
352                         {
353                             if (tag.category == null)
354                             {
355                                 Throw.when(main != null, XmlParserException.class,
356                                         "Multiple DEMAND tags define main demand from %s to %s.", origin.getId(),
357                                         destination.getId());
358                                 Throw.when(set.size() == 1, XmlParserException.class,
359                                         "Categorized demand from %s to %s has single DEMAND, and without category.",
360                                         origin.getId(), destination.getId());
361                                 main = tag;
362                             }
363                         }
364                     }
365                     for (DemandTag tag : set)
366                     {
367                         Throw.when(this.categorization.equals(Categorization.UNCATEGORIZED) && set.size() > 1,
368                                 XmlParserException.class, "Multiple DEMAND tags define demand from %s to %s.", origin.getId(),
369                                 destination.getId());
370                         if (tag.equals(main))
371                         {
372                             // skip main demand itself
373                             continue;
374                         }
375                         TimeVector timeVector = tag.timeVector == null
376                                 ? (main == null || main.timeVector == null ? this.globalTime : main.timeVector)
377                                 : tag.timeVector;
378                         Interpolation interpolation = tag.interpolation == null
379                                 ? (main == null || main.interpolation == null ? this.globalInterpolation : main.interpolation)
380                                 : tag.interpolation;
381                         // category
382                         Category category = this.categorization.equals(Categorization.UNCATEGORIZED) ? Category.UNCATEGORIZED
383                                 : tag.category;
384                         // sort out factor
385                         Double factor = globalFraction != null ? globalFraction : null;
386                         Double mainFraction = main == null ? null : main.factor;
387                         factor = nullMultiply(factor, mainFraction);
388                         factor = nullMultiply(factor, tag.factor);
389                         FrequencyVector demandVector = tag.demandVector;
390                         if (demandVector == null)
391                         {
392                             demandVector = main.demandVector;
393                         }
394                         if (tag.factors != null)
395                         {
396                             double[] factors;
397                             if (factor == null)
398                             {
399                                 factors = tag.factors;
400                             }
401                             else
402                             {
403                                 factors = new double[tag.factors.length];
404                                 for (int i = 0; i < tag.factors.length; i++)
405                                 {
406                                     factors[i] = tag.factors[i] * factor;
407                                 }
408                             }
409                             odMatrix.putDemandVector(origin, destination, category, demandVector, timeVector, interpolation,
410                                     factors);
411                         }
412                         else if (factor != null)
413                         {
414                             odMatrix.putDemandVector(origin, destination, category, demandVector, timeVector, interpolation,
415                                     factor);
416                         }
417                         else
418                         {
419                             odMatrix.putDemandVector(origin, destination, category, demandVector, timeVector, interpolation);
420                         }
421                     }
422                 }
423             }
424         }
425 
426         return odMatrix;
427     }
428 
429     /**
430      * Returns the multiplication of two values, taking 1.0 for {@code null} and returning {@code null} if both {@code null}.
431      * @param d1 Double; Double value 1
432      * @param d2 Double; Double value 2
433      * @return multiplication of two values, taking 1.0 for {@code null} and returning {@code null} if both {@code null}
434      */
435     final static Double nullMultiply(final Double d1, final Double d2)
436     {
437         if (d1 == null)
438         {
439             return d2;
440         }
441         if (d2 == null)
442         {
443             return d1;
444         }
445         return d1 * d2;
446     }
447 
448 }