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