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