View Javadoc
1   package org.opentrafficsim.road.network.factory.xml.demand;
2   
3   import static org.junit.Assert.assertTrue;
4   import static org.junit.Assert.fail;
5   
6   import java.io.ByteArrayInputStream;
7   import java.io.Serializable;
8   import java.nio.charset.StandardCharsets;
9   import java.util.LinkedHashMap;
10  import java.util.LinkedHashSet;
11  import java.util.Set;
12  
13  import javax.naming.NamingException;
14  
15  import org.djunits.value.vdouble.scalar.Direction;
16  import org.djunits.value.vdouble.scalar.Duration;
17  import org.djunits.value.vdouble.scalar.Length;
18  import org.djunits.value.vdouble.scalar.Time;
19  import org.junit.Test;
20  import org.opentrafficsim.core.dsol.AbstractOTSModel;
21  import org.opentrafficsim.core.dsol.OTSModelInterface;
22  import org.opentrafficsim.core.dsol.OTSSimulator;
23  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
24  import org.opentrafficsim.core.geometry.OTSGeometryException;
25  import org.opentrafficsim.core.geometry.OTSLine3D;
26  import org.opentrafficsim.core.geometry.OTSPoint3D;
27  import org.opentrafficsim.core.gtu.GTUType;
28  import org.opentrafficsim.core.network.LinkType;
29  import org.opentrafficsim.core.network.NetworkException;
30  import org.opentrafficsim.core.network.Node;
31  import org.opentrafficsim.core.network.route.Route;
32  import org.opentrafficsim.road.gtu.strategical.od.Category;
33  import org.opentrafficsim.road.gtu.strategical.od.ODMatrix;
34  import org.opentrafficsim.road.network.OTSRoadNetwork;
35  import org.opentrafficsim.road.network.factory.xml.XmlParserException;
36  import org.opentrafficsim.road.network.lane.CrossSectionLink;
37  import org.opentrafficsim.road.network.lane.Lane;
38  import org.opentrafficsim.road.network.lane.LaneType;
39  import org.opentrafficsim.road.network.lane.OTSRoadNode;
40  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
41  
42  import nl.tudelft.simulation.dsol.SimRuntimeException;
43  
44  /**
45   * <p>
46   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
47   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
48   * <p>
49   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 28 mei 2018 <br>
50   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
51   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
52   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
53   */
54  public class XmlOdParserTest
55  {
56  
57      /** GTU types. */
58      private Set<GTUType> gtuTypes = new LinkedHashSet<>();
59  
60      /** Simulator. */
61      OTSSimulatorInterface simulator = new OTSSimulator("XmlOdParserTest");
62  
63      /** Network. */
64      OTSRoadNetwork network = new OTSRoadNetwork("OD test", true, simulator);
65  
66      /** Parser. */
67      private XmlOdParser parser;
68  
69      /**
70       * Constructor.
71       * @throws NetworkException on error
72       * @throws OTSGeometryException on error
73       * @throws SimRuntimeException on error
74       * @throws NamingException on error
75       */
76      public XmlOdParserTest() throws NetworkException, OTSGeometryException, SimRuntimeException, NamingException
77      {
78          OTSModelInterface model = new AbstractOTSModel(this.simulator)
79          {
80              /** */
81              private static final long serialVersionUID = 1L;
82  
83              @Override
84              public void constructModel() throws SimRuntimeException
85              {
86                  //
87              }
88  
89              @Override
90              public OTSRoadNetwork getNetwork()
91              {
92                  return XmlOdParserTest.this.network;
93              }
94  
95              @Override
96              public Serializable getSourceId()
97              {
98                  return "XmlOdParserTest.Model";
99              }
100         };
101         this.simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600.0), model);
102         this.gtuTypes.add(this.network.getGtuType(GTUType.DEFAULTS.CAR));
103         this.gtuTypes.add(this.network.getGtuType(GTUType.DEFAULTS.TRUCK));
104         // TODO verify that Direction.ZERO will not cause problems...
105         OTSRoadNode A = new OTSRoadNode(this.network, "A", new OTSPoint3D(0, 0, 0), Direction.ZERO);
106         OTSRoadNode B = new OTSRoadNode(this.network, "B", new OTSPoint3D(1, 0, 0), Direction.ZERO);
107         OTSRoadNode C = new OTSRoadNode(this.network, "C", new OTSPoint3D(0, 1, 0), Direction.ZERO);
108         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE), new Route("AB").addNode(A).addNode(B));
109         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE),
110                 new Route("AB2").addNode(A).addNode(C).addNode(B));
111         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE), new Route("AC").addNode(A).addNode(C));
112         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE),
113                 new Route("AC2").addNode(A).addNode(B).addNode(C));
114         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE), new Route("BC").addNode(B).addNode(C));
115         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE),
116                 new Route("BC2").addNode(B).addNode(A).addNode(C));
117         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE), new Route("BA").addNode(B).addNode(A));
118         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE),
119                 new Route("BA2").addNode(B).addNode(C).addNode(A));
120         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE), new Route("CA").addNode(C).addNode(A));
121         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE),
122                 new Route("CA2").addNode(C).addNode(B).addNode(A));
123         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE), new Route("CB").addNode(C).addNode(B));
124         this.network.addRoute(this.network.getGtuType(GTUType.DEFAULTS.VEHICLE),
125                 new Route("CB2").addNode(C).addNode(A).addNode(B));
126         CrossSectionLink AB =
127                 new CrossSectionLink(this.network, "AB", A, B, this.network.getLinkType(LinkType.DEFAULTS.FREEWAY),
128                         new OTSLine3D(A.getPoint(), B.getPoint()), LaneKeepingPolicy.KEEPRIGHT);
129         new Lane(AB, "left", Length.ZERO, Length.ZERO, this.network.getLaneType(LaneType.DEFAULTS.FREEWAY), new LinkedHashMap<>());
130     }
131 
132     /**
133      * Tests OD validity. Checks that fails occur. Note: fails may occur for other reasons so these tests must be carefully
134      * created and tested not to fail by using the correct input once.
135      * @throws XmlParserException on error
136      */
137     @Test
138     public void ValidityTest() throws XmlParserException
139     {
140         StringBuilder xml;
141         ODMatrix od;
142 
143         // WS chosen to default to LINEAR, so may have no global interpolation
144         // xml = new StringBuilder();
145         // xml.append("<OD />");
146         // shouldFail(xml.toString(), "Parser should fail without GLOBALINTERPOLATION.");
147 
148         xml = new StringBuilder();
149         xml.append("<NOTOD GLOBALINTERPOLATION=\"LINEAR\">");
150         xml.append("  <GLOBALTIME />");
151         xml.append("</NOTOD>");
152         shouldFail(xml.toString(), "Parser should fail if main tag is not 'OD'.");
153 
154         xml = new StringBuilder();
155         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
156         xml.append("  <GLOBALTIME />");
157         xml.append("  <GLOBALTIME />");
158         xml.append("</OD>");
159         shouldFail(xml.toString(), "Parser should fail on multiple GLOBALTIME tags.");
160 
161         xml = new StringBuilder();
162         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
163         xml.append("  <GLOBALTIME>");
164         xml.append("    <TIME />");
165         xml.append("  </GLOBALTIME>");
166         xml.append("</OD>");
167         shouldFail(xml.toString(), "Parser should fail on missing VALUE in TIMEs.");
168 
169         xml = new StringBuilder();
170         xml.append("<OD GLOBALINTERPOLATION=\"NOTLINEAR\">");
171         xml.append("  <GLOBALTIME />");
172         xml.append("</OD>");
173         shouldFail(xml.toString(), "Parser should fail if GLOBALINTERPOLATION has bad value.");
174 
175         xml = new StringBuilder();
176         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\" NAME=\"ODNAME\">");
177         xml.append("  <GLOBALTIME />");
178         xml.append("</OD>");
179         // FAILS od = fromString(xml.toString());
180         // FAILS assertTrue("NAME of OD not correctly parsed.", od.getId().equals("ODNAME"));
181 
182         xml = new StringBuilder();
183         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
184         xml.append("  <GLOBALTIME>");
185         xml.append("    <TIME VALUE=\"0s\" />");
186         xml.append("    <TIME VALUE=\"1quark\" />");
187         xml.append("  </GLOBALTIME>");
188         xml.append("</OD>");
189         shouldFail(xml.toString(), "Parser should fail on bad time unit 'quark'.");
190 
191         xml = new StringBuilder();
192         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
193         xml.append("  <GLOBALTIME>");
194         xml.append("    <TIME VALUE=\"0s\" />");
195         xml.append("    <TIME VALUE=\"1h\" />");
196         xml.append("  </GLOBALTIME>");
197         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"D\">");
198         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
199         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
200         xml.append("  </DEMAND>");
201         xml.append("</OD>");
202         shouldFail(xml.toString(), "Parser should fail on unavailable node D.");
203 
204         xml = new StringBuilder();
205         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
206         xml.append("  <GLOBALTIME>");
207         xml.append("    <TIME VALUE=\"0s\" />");
208         xml.append("    <TIME VALUE=\"1h\" />");
209         xml.append("  </GLOBALTIME>");
210         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" CATEGORY=\"CAR\">");
211         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
212         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
213         xml.append("  </DEMAND>");
214         xml.append("</OD>");
215         shouldFail(xml.toString(), "Parser should fail if CATEGORY is used without it being defined.");
216 
217         xml = new StringBuilder();
218         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
219         xml.append("  <GLOBALTIME />");
220         xml.append("  <CATEGORY GTUTYPE=\"CAR\" />");
221         xml.append("</OD>");
222         shouldFail(xml.toString(), "Parser should fail if CATEGORY has no NAME.");
223 
224         xml = new StringBuilder();
225         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
226         xml.append("  <GLOBALTIME />");
227         xml.append("  <CATEGORY NAME=\"CAR\" />");
228         xml.append("</OD>");
229         shouldFail(xml.toString(), "Parser should fail if category is defined without object.");
230 
231         xml = new StringBuilder();
232         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
233         xml.append("  <GLOBALTIME />");
234         xml.append("  <CATEGORY NAME=\"BUS\" GTUTYPE=\"BUS\" />");
235         xml.append("</OD>");
236         shouldFail(xml.toString(), "Parser should fail if CATEGORY is defined with unavailable GTUType.");
237 
238         xml = new StringBuilder();
239         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
240         xml.append("  <GLOBALTIME />");
241         xml.append("  <CATEGORY NAME=\"BUS\" ROUTE=\"AD\" />");
242         xml.append("</OD>");
243         shouldFail(xml.toString(), "Parser should fail if CATEGORY is defined with unavailable ROUTE.");
244 
245         xml = new StringBuilder();
246         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
247         xml.append("  <GLOBALTIME />");
248         xml.append("  <CATEGORY NAME=\"BUS\" LANE=\"AB.right\" />");
249         xml.append("</OD>");
250         shouldFail(xml.toString(), "Parser should fail if CATEGORY is defined with unavailable LANE.");
251 
252         xml = new StringBuilder();
253         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
254         xml.append("  <GLOBALTIME>");
255         xml.append("    <TIME VALUE=\"0s\" />");
256         xml.append("    <TIME VALUE=\"1h\" />");
257         xml.append("  </GLOBALTIME>");
258         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\">");
259         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
260         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
261         xml.append("  </DEMAND>");
262         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\">");
263         xml.append("    <LEVEL VALUE=\"300veh/s\" />");
264         xml.append("    <LEVEL VALUE=\"400veh/s\" />");
265         xml.append("  </DEMAND>");
266         xml.append("</OD>");
267         shouldFail(xml.toString(), "Parser should fail if DEMAND is defined multiple times.");
268 
269         xml = new StringBuilder();
270         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
271         xml.append("  <GLOBALTIME>");
272         xml.append("    <TIME VALUE=\"0s\" />");
273         xml.append("    <TIME VALUE=\"1h\" />");
274         xml.append("  </GLOBALTIME>");
275         xml.append("  <CATEGORY NAME=\"CAR\" GTUTYPE=\"CAR\" />");
276         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\">");
277         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
278         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
279         xml.append("  </DEMAND>");
280         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\">");
281         xml.append("    <LEVEL VALUE=\"300veh/s\" />");
282         xml.append("    <LEVEL VALUE=\"400veh/s\" />");
283         xml.append("  </DEMAND>");
284         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" CATEGORY=\"CAR\" />");
285         xml.append("</OD>");
286         shouldFail(xml.toString(), "Parser should fail if main DEMAND is defined multiple times.");
287 
288         xml = new StringBuilder();
289         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
290         xml.append("  <GLOBALTIME>");
291         xml.append("    <TIME VALUE=\"0s\" />");
292         xml.append("    <TIME VALUE=\"1h\" />");
293         xml.append("  </GLOBALTIME>");
294         xml.append("  <CATEGORY NAME=\"CAR\" GTUTYPE=\"CAR\" />");
295         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\">");
296         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
297         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
298         xml.append("  </DEMAND>");
299         xml.append("</OD>");
300         shouldFail(xml.toString(), "Parser should fail if main DEMAND is defined multiple times.");
301 
302         xml = new StringBuilder();
303         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
304         xml.append("  <GLOBALTIME />");
305         xml.append("  <CATEGORY NAME=\"CAR\" GTUTYPE=\"CAR\" />");
306         xml.append("  <CATEGORY NAME=\"TRUCK\" GTUTYPE=\"TRUCK\" ROUTE=\"AB\" />");
307         xml.append("</OD>");
308         shouldFail(xml.toString(), "Parser should fail if categories apply different categorizations.");
309 
310         xml = new StringBuilder();
311         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
312         xml.append("  <GLOBALTIME />");
313         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" />");
314         xml.append("</OD>");
315         shouldFail(xml.toString(), "Parser should fail if DEMAND is without category and LEVEL data.");
316 
317         xml = new StringBuilder();
318         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
319         xml.append("  <GLOBALTIME />");
320         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\">");
321         xml.append("    <LEVEL VALUE=\"2\" />");
322         xml.append("    <LEVEL VALUE=\"3\" />");
323         xml.append("  </DEMAND>");
324         xml.append("</OD>");
325         shouldFail(xml.toString(), "Parser should fail if DEMAND is without category and LEVEL data.");
326 
327         xml = new StringBuilder();
328         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
329         xml.append("  <GLOBALTIME />");
330         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\">");
331         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
332         xml.append("    <LEVEL />");
333         xml.append("  </DEMAND>");
334         xml.append("</OD>");
335         shouldFail(xml.toString(), "Parser should fail if LEVEL is missing VALUE attribute.");
336     }
337 
338     /**
339      * Tests that demand levels are correct, including fractions.
340      * @throws XmlParserException on error
341      */
342     @Test
343     public void LevelTest() throws XmlParserException
344     {
345         StringBuilder xml;
346         ODMatrix od;
347 
348         Node A = this.network.getNode("A");
349         Node B = this.network.getNode("B");
350 
351         xml = new StringBuilder();
352         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
353         xml.append("  <GLOBALTIME>");
354         xml.append("    <TIME VALUE=\"0s\" />");
355         xml.append("    <TIME VALUE=\"1h\" />");
356         xml.append("  </GLOBALTIME>");
357         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\">");
358         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
359         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
360         xml.append("  </DEMAND>");
361         xml.append("</OD>");
362         od = fromString(xml.toString());
363         assertAboutEquals(od.getDemand(A, B, Category.UNCATEGORIZED, Time.instantiateSI(1800), true).si, 150);
364 
365         // the next tests will keep adding factors, and check whether the demand increases accordingly
366         xml = new StringBuilder();
367         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\">");
368         xml.append("  <GLOBALTIME>");
369         xml.append("    <TIME VALUE=\"0s\" />");
370         xml.append("    <TIME VALUE=\"1h\" />");
371         xml.append("  </GLOBALTIME>");
372         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" FACTOR=\"2\">"); // x2
373         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
374         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
375         xml.append("  </DEMAND>");
376         xml.append("</OD>");
377         od = fromString(xml.toString());
378         assertAboutEquals(od.getDemand(A, B, Category.UNCATEGORIZED, Time.instantiateSI(1800), true).si, 300);
379 
380         xml = new StringBuilder();
381         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\" GLOBALFACTOR=\"2\">"); // x2
382         xml.append("  <GLOBALTIME>");
383         xml.append("    <TIME VALUE=\"0s\" />");
384         xml.append("    <TIME VALUE=\"1h\" />");
385         xml.append("  </GLOBALTIME>");
386         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" FACTOR=\"2\">");
387         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
388         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
389         xml.append("  </DEMAND>");
390         xml.append("</OD>");
391         od = fromString(xml.toString());
392         assertAboutEquals(od.getDemand(A, B, Category.UNCATEGORIZED, Time.instantiateSI(1800), true).si, 600);
393 
394         xml = new StringBuilder();
395         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\" GLOBALFACTOR=\"2\">");
396         xml.append("  <GLOBALTIME>");
397         xml.append("    <TIME VALUE=\"0s\" />");
398         xml.append("    <TIME VALUE=\"1h\" />");
399         xml.append("  </GLOBALTIME>");
400         xml.append("  <CATEGORY NAME=\"CAR\" GTUTYPE=\"CAR\" FACTOR=\"2\" />"); // x2
401         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" CATEGORY=\"CAR\" FACTOR=\"2\">");
402         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
403         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
404         xml.append("  </DEMAND>");
405         xml.append("</OD>");
406         od = fromString(xml.toString());
407         assertAboutEquals(od.getDemand(A, B, getCategory("CAR"), Time.instantiateSI(1800), true).si, 1200);
408 
409         xml = new StringBuilder();
410         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\" GLOBALFACTOR=\"2\">");
411         xml.append("  <GLOBALTIME>");
412         xml.append("    <TIME VALUE=\"0s\" />");
413         xml.append("    <TIME VALUE=\"1h\" />");
414         xml.append("  </GLOBALTIME>");
415         xml.append("  <CATEGORY NAME=\"CAR\" GTUTYPE=\"CAR\" FACTOR=\"2\" />");
416         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" >");
417         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
418         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
419         xml.append("  </DEMAND>");
420         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" CATEGORY=\"CAR\" FACTOR=\"2\" >");
421         xml.append("    <LEVEL VALUE=\"2\" />"); // x2
422         xml.append("    <LEVEL VALUE=\"2\" />");
423         xml.append("  </DEMAND>");
424         xml.append("</OD>");
425         od = fromString(xml.toString());
426         assertAboutEquals(od.getDemand(A, B, getCategory("CAR"), Time.instantiateSI(1800), true).si, 2400);
427 
428         xml = new StringBuilder();
429         xml.append("<OD GLOBALINTERPOLATION=\"LINEAR\" GLOBALFACTOR=\"2\">");
430         xml.append("  <GLOBALTIME>");
431         xml.append("    <TIME VALUE=\"0s\" />");
432         xml.append("    <TIME VALUE=\"1h\" />");
433         xml.append("  </GLOBALTIME>");
434         xml.append("  <CATEGORY NAME=\"CAR\" GTUTYPE=\"CAR\" FACTOR=\"2\" />");
435         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" FACTOR=\"2\" >"); // x2
436         xml.append("    <LEVEL VALUE=\"100veh/s\" />");
437         xml.append("    <LEVEL VALUE=\"200veh/s\" />");
438         xml.append("  </DEMAND>");
439         xml.append("  <DEMAND ORIGIN=\"A\" DESTINATION=\"B\" CATEGORY=\"CAR\" FACTOR=\"2\" >");
440         xml.append("    <LEVEL VALUE=\"2\" />");
441         xml.append("    <LEVEL VALUE=\"2\" />");
442         xml.append("  </DEMAND>");
443         xml.append("</OD>");
444         od = fromString(xml.toString());
445         assertAboutEquals(od.getDemand(A, B, getCategory("CAR"), Time.instantiateSI(1800), true).si, 4800);
446     }
447 
448     /**
449      * Fails on value being more than 1e-6 apart.
450      * @param obtained double; obtained value
451      * @param expected double; expected value
452      */
453     private void assertAboutEquals(final double obtained, final double expected)
454     {
455         if (Math.abs(obtained - expected) > 1e-6)
456         {
457             fail(String.format("Demand incorrect, expected %.3f, obtained %.3f.", expected, obtained));
458         }
459     }
460 
461     /**
462      * Returns the category of given name.
463      * @param name String; name
464      * @return Category; category of given name
465      */
466     private Category getCategory(final String name)
467     {
468         return this.parser.categories.get("CAR").getCategory(this.parser.categorization);
469     }
470 
471     /**
472      * Creates an OD from a string.
473      * @param str String; xml code of OD
474      * @return InputStream
475      * @throws XmlParserException on error
476      */
477     private ODMatrix fromString(final String str) throws XmlParserException
478     {
479         this.parser = new XmlOdParser(this.simulator, this.network, this.gtuTypes);
480         return this.parser.build(new ByteArrayInputStream(
481                 ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + str).getBytes(StandardCharsets.UTF_8)));
482     }
483 
484     /**
485      * Tests a build that should fail.
486      * @param xml String; XML string
487      * @param message String; message if it doesn't fail
488      */
489     public void shouldFail(final String xml, final String message)
490     {
491         try
492         {
493             fromString(xml);
494             fail(message);
495         }
496         catch (XmlParserException e)
497         {
498             // e.printStackTrace(); // Can be used to check that fails occur for the right reason
499             // expected
500         }
501     }
502 
503 }