View Javadoc
1   package org.opentrafficsim.road.network.factory.xml.parser;
2   
3   import java.math.BigInteger;
4   import java.util.ArrayList;
5   import java.util.Collections;
6   import java.util.Comparator;
7   import java.util.LinkedHashMap;
8   import java.util.LinkedHashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  
13  import org.djunits.value.StorageType;
14  import org.djunits.value.vdouble.scalar.Acceleration;
15  import org.djunits.value.vdouble.scalar.Duration;
16  import org.djunits.value.vdouble.scalar.Frequency;
17  import org.djunits.value.vdouble.scalar.Length;
18  import org.djunits.value.vdouble.scalar.Speed;
19  import org.djunits.value.vdouble.scalar.Time;
20  import org.djunits.value.vdouble.vector.FrequencyVector;
21  import org.djunits.value.vdouble.vector.TimeVector;
22  import org.djutils.exceptions.Throw;
23  import org.djutils.exceptions.Try;
24  import org.opentrafficsim.core.distributions.Generator;
25  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
26  import org.opentrafficsim.core.gtu.GTUException;
27  import org.opentrafficsim.core.gtu.GTUType;
28  import org.opentrafficsim.core.gtu.NestedCache;
29  import org.opentrafficsim.core.gtu.TemplateGTUType;
30  import org.opentrafficsim.core.idgenerator.IdGenerator;
31  import org.opentrafficsim.core.network.Link;
32  import org.opentrafficsim.core.network.Node;
33  import org.opentrafficsim.core.network.route.Route;
34  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBias;
35  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
36  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.RoadPosition;
37  import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator.RoomChecker;
38  import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator;
39  import org.opentrafficsim.road.gtu.generator.MarkovCorrelation;
40  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGTUCharacteristicsGenerator;
41  import org.opentrafficsim.road.gtu.generator.od.DefaultGTUCharacteristicsGeneratorOD.Factory;
42  import org.opentrafficsim.road.gtu.generator.od.ODApplier.GeneratorObjects;
43  import org.opentrafficsim.road.gtu.generator.od.ODApplier;
44  import org.opentrafficsim.road.gtu.generator.od.ODOptions;
45  import org.opentrafficsim.road.gtu.generator.od.ODOptions.Option;
46  import org.opentrafficsim.road.gtu.generator.od.StrategicalPlannerFactorySupplierOD;
47  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusFactory;
48  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLMRSPerceptionFactory;
49  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRSFactory;
50  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
51  import org.opentrafficsim.road.gtu.strategical.od.Categorization;
52  import org.opentrafficsim.road.gtu.strategical.od.Category;
53  import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
54  import org.opentrafficsim.road.gtu.strategical.od.ODMatrix;
55  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlannerFactory;
56  import org.opentrafficsim.road.gtu.strategical.route.RouteGeneratorOD;
57  import org.opentrafficsim.road.network.OTSRoadNetwork;
58  import org.opentrafficsim.road.network.factory.xml.XmlParserException;
59  import org.opentrafficsim.road.network.factory.xml.utils.ParseDistribution;
60  import org.opentrafficsim.road.network.factory.xml.utils.StreamInformation;
61  import org.opentrafficsim.road.network.factory.xml.utils.Transformer;
62  import org.opentrafficsim.road.network.lane.CrossSectionLink;
63  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
64  import org.opentrafficsim.road.network.lane.Lane;
65  import org.opentrafficsim.xml.generated.CATEGORYTYPE;
66  import org.opentrafficsim.xml.generated.GENERATOR;
67  import org.opentrafficsim.xml.generated.GLOBALTIMETYPE.TIME;
68  import org.opentrafficsim.xml.generated.GTUTEMPLATE;
69  import org.opentrafficsim.xml.generated.GTUTEMPLATEMIX;
70  import org.opentrafficsim.xml.generated.LEVELTIMETYPE;
71  import org.opentrafficsim.xml.generated.LISTGENERATOR;
72  import org.opentrafficsim.xml.generated.NETWORKDEMAND;
73  import org.opentrafficsim.xml.generated.OD;
74  import org.opentrafficsim.xml.generated.OD.DEMAND;
75  import org.opentrafficsim.xml.generated.ODOPTIONS;
76  import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM;
77  import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM.DEFAULTMODEL;
78  import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM.LANEBIASES.LANEBIAS;
79  import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM.MARKOV.STATE;
80  import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM.MODEL;
81  import org.opentrafficsim.xml.generated.SINK;
82  
83  import nl.tudelft.simulation.jstats.streams.StreamInterface;
84  
85  /**
86   * <p>
87   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
88   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
89   * <p>
90   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Mar 29, 2019 <br>
91   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
92   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
93   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
94   */
95  
96  public class DemandParser
97  {
98      /** */
99      private DemandParser()
100     {
101         // static class
102     }
103 
104     /**
105      * Creates generators and returns OD matrices.
106      * @param otsNetwork OTSRoadNetwork; network
107      * @param simulator OTSSimulatorInterface; simulator
108      * @param demands List&lt;NETWORKDEMAND&gt;; demand
109      * @param gtuTemplates Map&lt;String, GTUTEMPLATE&gt;; GTU templates
110      * @param factories Map&lt;String, LaneBasedStrategicalPlannerFactory&lt;?&gt;&gt;; factories from model parser
111      * @param modelIdReferrals Map&lt;String, String&gt;; model id referrals
112      * @param streamMap Map&lt;String, StreamInformation&gt;; stream map
113      * @return List&lt;LaneBasedGTUGenerator&gt;; generators
114      * @throws XmlParserException if the OD contains an inconsistency or error
115      */
116     public static List<LaneBasedGTUGenerator> parseDemand(final OTSRoadNetwork otsNetwork,
117             final OTSSimulatorInterface simulator, final List<NETWORKDEMAND> demands,
118             final Map<String, GTUTEMPLATE> gtuTemplates, Map<String, LaneBasedStrategicalPlannerFactory<?>> factories,
119             final Map<String, String> modelIdReferrals, final Map<String, StreamInformation> streamMap)
120             throws XmlParserException
121     {
122         List<LaneBasedGTUGenerator> generators = new ArrayList<>();
123 
124         IdGenerator idGenerator = new IdGenerator("");
125 
126         int idCounter = 1;
127         for (NETWORKDEMAND subDemand : demands)
128         {
129 
130             // Map<String>
131             for (GTUTEMPLATEMIX gtuMix : subDemand.getGTUTEMPLATEMIX())
132             {
133 
134             }
135 
136             for (GENERATOR generator : subDemand.getGENERATOR())
137             {
138                 String linkId = generator.getLINK();
139                 String laneId = null;
140 
141                 String id = linkId + "." + laneId + "." + idCounter;
142                 Generator<Duration> interarrivelTimeGenerator;
143                 RoomChecker roomChecker;
144                 LaneBasedGTUCharacteristicsGenerator laneBasedGTUCharacteristicsGenerator;
145                 // Location
146 
147                 Link link = otsNetwork.getLink(linkId);
148                 Throw.when(!(link instanceof CrossSectionLink), XmlParserException.class,
149                         "Generator on link %s can not be added as the link is not a CrossSectionLink.", linkId);
150                 // Lane lane = link.
151                 Set<DirectedLanePosition> positions = new LinkedHashSet<>();
152                 // GeneratorPositions generatorPositions = GeneratorPositions.create(positions, stream);
153 
154                 // LaneBasedGTUGenerator gen = new LaneBasedGTUGenerator(id, interarrivelTimeGenerator,
155                 // laneBasedGTUCharacteristicsGenerator, generatorPositions, otsNetwork, simulator, roomChecker, idGenerator);
156             }
157 
158             for (LISTGENERATOR generator : subDemand.getLISTGENERATOR())
159             {
160 
161             }
162 
163             for (SINK sink : subDemand.getSINK())
164             {
165 
166             }
167 
168             // Collect options
169             Map<String, ODOPTIONS> odOptionsMap = new LinkedHashMap<>();
170             for (ODOPTIONS odOptions : subDemand.getODOPTIONS())
171             {
172                 odOptionsMap.put(odOptions.getID(), odOptions);
173             }
174             List<OD> ods = subDemand.getOD();
175             for (OD od : ods)
176             {
177 
178                 // ID
179                 String id = od.getID();
180 
181                 // Origins and destinations, retrieve them from demand items
182                 List<Node> origins = new ArrayList<>();
183                 List<Node> destinations = new ArrayList<>();
184                 for (DEMAND demand : od.getDEMAND())
185                 {
186                     if (!origins.contains(otsNetwork.getNode(demand.getORIGIN())))
187                     {
188                         origins.add(otsNetwork.getNode(demand.getORIGIN()));
189                     }
190                     if (!destinations.contains(otsNetwork.getNode(demand.getDESTINATION())))
191                     {
192                         destinations.add(otsNetwork.getNode(demand.getDESTINATION()));
193                     }
194                 }
195 
196                 // Create categorization
197                 Categorization categorization;
198                 Map<String, Category> categories = new LinkedHashMap<>();
199                 Map<String, Double> categoryFactors = new LinkedHashMap<>();
200                 if (od.getCATEGORY() == null || od.getCATEGORY().isEmpty())
201                 {
202                     categorization = Categorization.UNCATEGORIZED;
203                 }
204                 else
205                 {
206                     List<Class<?>> categoryClasses = new ArrayList<>();
207                     if (od.getCATEGORY().get(0).getGTUTYPE() != null)
208                     {
209                         categoryClasses.add(GTUType.class);
210                     }
211                     if (od.getCATEGORY().get(0).getROUTE() != null)
212                     {
213                         categoryClasses.add(Route.class);
214                     }
215                     if (od.getCATEGORY().get(0).getLANE() != null)
216                     {
217                         categoryClasses.add(Lane.class);
218                     }
219                     if (categoryClasses.isEmpty())
220                     {
221                         // XML uses categories, but these define nothing
222                         categorization = Categorization.UNCATEGORIZED;
223                     }
224                     else
225                     {
226                         Class<?> clazz = categoryClasses.get(0);
227                         categoryClasses.remove(0);
228                         categorization = new Categorization("", clazz, categoryClasses.toArray(new Class[0]));
229                     }
230                     // create categories and check that all categories comply with the categorization
231                     for (CATEGORYTYPE category : od.getCATEGORY())
232                     {
233                         Throw.when(
234                                 (categorization.entails(GTUType.class) && category.getGTUTYPE() == null)
235                                         || (!categorization.entails(GTUType.class) && category.getGTUTYPE() != null),
236                                 XmlParserException.class, "Categories are inconsistent concerning GTUType.");
237                         Throw.when(
238                                 (categorization.entails(Route.class) && category.getROUTE() == null)
239                                         || (!categorization.entails(Route.class) && category.getROUTE() != null),
240                                 XmlParserException.class, "Categories are inconsistent concerning Route.");
241                         Throw.when(
242                                 (categorization.entails(Lane.class) && category.getLANE() == null)
243                                         || (!categorization.entails(Lane.class) && category.getLANE() != null),
244                                 XmlParserException.class, "Categories are inconsistent concerning Lane.");
245                         List<Object> objects = new ArrayList<>();
246                         if (categorization.entails(GTUType.class))
247                         {
248                             objects.add(otsNetwork.getGtuType(category.getGTUTYPE()));
249                         }
250                         if (categorization.entails(Route.class))
251                         {
252                             objects.add(otsNetwork.getRoute(category.getROUTE()));
253                         }
254                         if (categorization.entails(Lane.class))
255                         {
256                             CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(category.getLANE().getLINK());
257                             Lane lane = (Lane) link.getCrossSectionElement(category.getLANE().getID());
258                             objects.add(lane);
259                         }
260                         categories.put(category.getID(), new Category(categorization, objects.get(0),
261                                 objects.subList(1, objects.size()).toArray(new Object[objects.size() - 1])));
262                         categoryFactors.put(category.getID(), parsePositiveFactor(category.getFACTOR()));
263                     }
264                 }
265 
266                 // Global time vector
267                 TimeVector globalTimeVector = null;
268                 if (od.getGLOBALTIME() != null)
269                 {
270                     List<Time> timeList = new ArrayList<>();
271                     for (TIME time : od.getGLOBALTIME().getTIME())
272                     {
273                         timeList.add(time.getVALUE());
274                     }
275                     Collections.sort(timeList);
276                     globalTimeVector = Try.assign(() -> new TimeVector(timeList, StorageType.DENSE), XmlParserException.class,
277                             "Global time has no values.");
278                 }
279 
280                 // Global interpolation
281                 Interpolation globalInterpolation =
282                         od.getGLOBALINTERPOLATION().equals("LINEAR") ? Interpolation.LINEAR : Interpolation.STEPWISE;
283 
284                 // Global factor
285                 double globalFactor = parsePositiveFactor(od.getGLOBALFACTOR());
286 
287                 // Create the OD matrix
288                 ODMatrix odMatrix =
289                         new ODMatrix(id, origins, destinations, categorization, globalTimeVector, globalInterpolation);
290 
291                 // Add demand
292                 NestedCache<Set<DEMAND>> demandPerOD = new NestedCache<>(Node.class, Node.class);
293                 for (DEMAND demand : od.getDEMAND())
294                 {
295                     Node origin = otsNetwork.getNode(demand.getORIGIN());
296                     Node destination = otsNetwork.getNode(demand.getDESTINATION());
297                     demandPerOD.getValue(() -> new LinkedHashSet<>(), origin, destination).add(demand);
298                 }
299                 for (Object o : demandPerOD.getKeys())
300                 {
301                     NestedCache<Set<DEMAND>> demandPerD = demandPerOD.getChild(o);
302                     for (Object d : demandPerD.getKeys())
303                     {
304                         Set<DEMAND> set = demandPerD.getValue(d);
305                         Node origin = (Node) o;
306                         Node destination = (Node) d;
307                         Throw.when(categorization.equals(Categorization.UNCATEGORIZED) && set.size() > 1,
308                                 XmlParserException.class,
309                                 "Multiple DEMAND tags define demand from %s to %s in uncategorized demand.", origin.getId(),
310                                 destination.getId());
311 
312                         // Find main demand, that may be split among other DEMAND tags between the same origin and destination
313                         DEMAND main = null;
314                         if (!categorization.equals(Categorization.UNCATEGORIZED))
315                         {
316                             for (DEMAND demand : set)
317                             {
318                                 if (demand.getCATEGORY() == null)
319                                 {
320                                     Throw.when(main != null, XmlParserException.class,
321                                             "Multiple DEMAND tags define main demand from %s to %s.", origin.getId(),
322                                             destination.getId());
323                                     Throw.when(set.size() == 1, XmlParserException.class,
324                                             "Categorized demand from %s to %s has single DEMAND, and without category.",
325                                             origin.getId(), destination.getId());
326                                     main = demand;
327                                 }
328                             }
329                         }
330 
331                         // Add demand per tag
332                         for (DEMAND demand : set)
333                         {
334                             // Skip main demand, it is split among other tags
335                             if (demand.equals(main))
336                             {
337                                 continue;
338                             }
339 
340                             // TimeVector: demand > main demand > global
341                             List<LEVELTIMETYPE> timeTags =
342                                     demand.getLEVEL() == null || demand.getLEVEL().get(0).getTIME() == null
343                                             ? (main == null || main.getLEVEL() == null
344                                                     || main.getLEVEL().get(0).getTIME() == null ? null : main.getLEVEL())
345                                             : demand.getLEVEL();
346                             TimeVector timeVector = timeTags == null ? globalTimeVector : parseTimeVector(timeTags);
347 
348                             // Interpolation: demand > main demand > global
349                             String interpolationString = demand.getINTERPOLATION() == null
350                                     ? (main == null || main.getINTERPOLATION() == null ? null : main.getINTERPOLATION())
351                                     : demand.getINTERPOLATION();
352                             Interpolation interpolation = interpolationString == null ? globalInterpolation
353                                     : interpolationString.equals("LINEAR") ? Interpolation.LINEAR : Interpolation.STEPWISE;
354 
355                             // Category
356                             Category category = categorization.equals(Categorization.UNCATEGORIZED) ? Category.UNCATEGORIZED
357                                     : categories.get(demand.getCATEGORY());
358 
359                             // Factor
360                             double factor = globalFactor;
361                             factor = main == null ? factor : factor * parsePositiveFactor(main.getFACTOR());
362                             factor *= parsePositiveFactor(demand.getFACTOR());
363 
364                             // Figure out where the base demand, and optional factors are
365                             Frequency[] demandRaw = new Frequency[timeVector.size()];
366                             List<LEVELTIMETYPE> baseDemand;
367                             List<LEVELTIMETYPE> factors = null;
368                             if (demand.getLEVEL() == null)
369                             {
370                                 // this demand specified no levels, use main demand
371                                 baseDemand = main.getLEVEL();
372                             }
373                             else if (demand.getLEVEL().get(0).getValue().contains("veh"))
374                             {
375                                 // this demand specifies levels
376                                 baseDemand = demand.getLEVEL();
377                             }
378                             else
379                             {
380                                 // this demand specifies factors on the main demand
381                                 baseDemand = main.getLEVEL();
382                                 factors = demand.getLEVEL();
383                             }
384                             // sort
385                             sortLevelTime(baseDemand);
386                             if (factors != null)
387                             {
388                                 sortLevelTime(factors);
389                             }
390                             // fill array, include factors
391                             for (int i = 0; i < baseDemand.size(); i++)
392                             {
393                                 Throw.when(
394                                         baseDemand.get(i).getTIME() != null && factors != null
395                                                 && factors.get(i).getTIME() != null
396                                                 && !baseDemand.get(i).getTIME().eq(factors.get(i).getTIME()),
397                                         XmlParserException.class,
398                                         "Demand from %s to %s is specified with factors that have different time from the base demand.",
399                                         origin, destination);
400                                 demandRaw[i] = parseLevel(baseDemand.get(i).getValue(),
401                                         factor * (factors == null ? 1.0 : parsePositiveFactor(factors.get(i).getValue())));
402                             }
403                             FrequencyVector demandVector = Try.assign(() -> new FrequencyVector(demandRaw, StorageType.DENSE),
404                                     XmlParserException.class, "Unexpected empty demand.");
405 
406                             // Finally, add the demand
407                             odMatrix.putDemandVector(origin, destination, category, demandVector, timeVector, interpolation);
408                         }
409 
410                     }
411                 }
412 
413                 // OD Options
414                 ODOptions odOptions = new ODOptions().set(ODOptions.GTU_ID, idGenerator);
415                 // templates
416                 Set<TemplateGTUType> templates = new LinkedHashSet<>();
417                 for (GTUTEMPLATE template : gtuTemplates.values())
418                 {
419                     GTUType gtuType = otsNetwork.getGtuType(template.getGTUTYPE());
420                     Generator<Length> lengthGenerator = ParseDistribution.parseLengthDist(streamMap, template.getLENGTHDIST());
421                     Generator<Length> widthGenerator = ParseDistribution.parseLengthDist(streamMap, template.getWIDTHDIST());
422                     Generator<Speed> maximumSpeedGenerator =
423                             ParseDistribution.parseSpeedDist(streamMap, template.getMAXSPEEDDIST());
424                     if (template.getMAXACCELERATIONDIST() == null || template.getMAXDECELERATIONDIST() == null)
425                     {
426                         templates.add(new TemplateGTUType(gtuType, lengthGenerator, widthGenerator, maximumSpeedGenerator));
427                     }
428                     else
429                     {
430                         Generator<Acceleration> maxAccelerationGenerator =
431                                 ParseDistribution.parseAccelerationDist(streamMap, template.getMAXACCELERATIONDIST());
432                         Generator<Acceleration> maxDecelerationGenerator =
433                                 ParseDistribution.parseAccelerationDist(streamMap, template.getMAXDECELERATIONDIST());
434                         templates.add(new TemplateGTUType(gtuType, lengthGenerator, widthGenerator, maximumSpeedGenerator,
435                                 maxAccelerationGenerator, maxDecelerationGenerator));
436                     }
437                 }
438                 // default global option to integrate defined templates
439                 Factory factory = new Factory(); // DefaultGTUCharacteristicsGeneratorOD factory
440                 factory.setTemplates(templates);
441                 odOptions.set(ODOptions.GTU_TYPE, factory.create());
442                 // other options
443                 if (od.getOPTIONS() != null)
444                 {
445                     Throw.when(!odOptionsMap.containsKey(od.getOPTIONS()), XmlParserException.class,
446                             "OD options of id od.getOPTIONS() not defined.");
447                     for (ODOPTIONSITEM options : odOptionsMap.get(od.getOPTIONS()).getODOPTIONSITEM())
448                     {
449                         /*
450                          * The current 'options' is valid within a single context, i.e. global, link type, origin or lane. All
451                          * option values are set in odOptions for that context, in the current loop. For the model factories an
452                          * implementation of StrategicalPlannerFactorySupplierOD is created that responds to the GTU type, and
453                          * selects a factory assigned to that GTU type within the context. Or, the default factory in the
454                          * context is used. Or finally, a default LMRS. If no model factory is specified in the context (nor a
455                          * higher context), no option value is set and ODOptions itself returns a default LMRS factory.
456                          */
457 
458                         // GTU type (model)
459                         if (options.getDEFAULTMODEL() != null || (options.getMODEL() != null && !options.getMODEL().isEmpty()))
460                         {
461                             LaneBasedStrategicalPlannerFactory<?> defaultFactory;
462                             if (options.getDEFAULTMODEL() != null)
463                             {
464                                 // TODO: model id referral
465                                 String modelId = DemandParser.getModelId(options.getDEFAULTMODEL(), modelIdReferrals);
466                                 Throw.when(!factories.containsKey(modelId), XmlParserException.class,
467                                         "OD option DEFAULTMODEL refers to a non-existent model with ID %s.", modelId);
468                                 defaultFactory = factories.get(modelId);
469                             }
470                             else
471                             {
472                                 defaultFactory = null;
473                             }
474                             // compose map that couples GTU types to factories through MODEL ID's
475                             final Map<GTUType, LaneBasedStrategicalPlannerFactory<?>> gtuTypeFactoryMap = new LinkedHashMap<>();
476                             if (options.getMODEL() != null)
477                             {
478                                 for (MODEL model : options.getMODEL())
479                                 {
480                                     GTUType gtuType = otsNetwork.getGtuType(model.getGTUTYPE());
481                                     Throw.when(!factories.containsKey(model.getID()), XmlParserException.class,
482                                             "OD option MODEL refers to a non existent-model with ID %s.", model.getID());
483                                     gtuTypeFactoryMap.put(gtuType, factories.get(getModelId(model, modelIdReferrals)));
484                                 }
485                             }
486                             factory = new Factory(); // DefaultGTUCharacteristicsGeneratorOD factory
487                             factory.setTemplates(templates);
488                             factory.setFactorySupplier(new StrategicalPlannerFactorySupplierOD()
489                             {
490                                 /** {@inheritDoc} */
491                                 @Override
492                                 public LaneBasedStrategicalPlannerFactory<?> getFactory(final Node origin,
493                                         final Node destination, final Category category, final StreamInterface randomStream)
494                                         throws GTUException
495                                 {
496                                     if (category.getCategorization().entails(GTUType.class))
497                                     {
498                                         LaneBasedStrategicalPlannerFactory<?> strategicalPlannerFactory =
499                                                 gtuTypeFactoryMap.get(category.get(GTUType.class));
500                                         if (strategicalPlannerFactory != null)
501                                         {
502                                             // a model factory for this GTU type is specified
503                                             return strategicalPlannerFactory;
504                                         }
505                                     }
506                                     if (defaultFactory != null)
507                                     {
508                                         // a default model factory is specified
509                                         return defaultFactory;
510                                     }
511                                     // no model factory specified, return a default LMRS factory
512                                     // TODO: LMRSFactory can receive a parameter factory, but how to define those parameters in
513                                     // XML?
514                                     return new LaneBasedStrategicalRoutePlannerFactory(
515                                             new LMRSFactory(new IDMPlusFactory(randomStream),
516                                                     new DefaultLMRSPerceptionFactory()),
517                                             RouteGeneratorOD.getDefaultRouteSupplier(randomStream));
518                                 }
519                             });
520                             setOption(odOptions, ODOptions.GTU_TYPE, factory.create(), options, otsNetwork);
521                         }
522                         // no lc
523                         setOption(odOptions, ODOptions.NO_LC_DIST, options.getNOLANECHANGE(), options, otsNetwork);
524                         // room checker
525                         setOption(odOptions, ODOptions.ROOM_CHECKER, Transformer.parseRoomChecker(options.getROOMCHECKER()),
526                                 options, otsNetwork);
527                         // headway distribution
528                         try
529                         {
530                             setOption(odOptions, ODOptions.HEADWAY_DIST,
531                                     Transformer.parseHeadwayDistribution(options.getHEADWAYDIST()), options, otsNetwork);
532                         }
533                         catch (NoSuchFieldException | IllegalAccessException exception)
534                         {
535                             throw new XmlParserException(exception);
536                         }
537                         // markov
538                         if (options.getMARKOV() != null)
539                         {
540                             Throw.when(!categorization.entails(GTUType.class), XmlParserException.class,
541                                     "The OD option MARKOV can only be used if GTUType is in the CATEGORY's.");
542                             Throw.when(!categorization.entails(Lane.class) && options.getLANE() != null,
543                                     XmlParserException.class,
544                                     "Markov chains at lane level are not used if Lane's are not in the CATEGORY's.");
545                             MarkovCorrelation<GTUType, Frequency> markov = new MarkovCorrelation<>();
546                             for (STATE state : options.getMARKOV().getSTATE())
547                             {
548                                 GTUType gtuType = otsNetwork.getGtuType(state.getGTUTYPE());
549                                 double correlation = state.getCORRELATION();
550                                 if (state.getPARENT() == null)
551                                 {
552                                     markov.addState(gtuType, correlation);
553                                 }
554                                 else
555                                 {
556                                     GTUType parentType = otsNetwork.getGtuType(state.getPARENT());
557                                     markov.addState(parentType, gtuType, correlation);
558                                 }
559                             }
560                             setOption(odOptions, ODOptions.MARKOV, markov, options, otsNetwork);
561                         }
562                         // lane biases
563                         if (options.getLANEBIASES() != null)
564                         {
565                             LaneBiases laneBiases = new LaneBiases();
566                             for (LANEBIAS laneBias : options.getLANEBIASES().getLANEBIAS())
567                             {
568                                 GTUType gtuType = otsNetwork.getGtuType(laneBias.getGTUTYPE());
569                                 double bias = laneBias.getBIAS();
570                                 int stickyLanes;
571                                 if (laneBias.getSTICKYLANES() == null)
572                                 {
573                                     stickyLanes = Integer.MAX_VALUE;
574                                 }
575                                 else
576                                 {
577                                     if (laneBias.getSTICKYLANES().compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0)
578                                     {
579                                         stickyLanes = Integer.MAX_VALUE;
580                                     }
581                                     else
582                                     {
583                                         stickyLanes = laneBias.getSTICKYLANES().intValue();
584                                     }
585                                 }
586                                 RoadPosition roadPosition;
587                                 if (laneBias.getFROMRIGHT() != null)
588                                 {
589                                     roadPosition = new RoadPosition.ByValue(laneBias.getFROMRIGHT());
590                                 }
591                                 else if (laneBias.getFROMLEFT() != null)
592                                 {
593                                     roadPosition = new RoadPosition.ByValue(1.0 - laneBias.getFROMLEFT());
594                                 }
595                                 else
596                                 {
597                                     roadPosition = new RoadPosition.BySpeed(laneBias.getLEFTSPEED(), laneBias.getRIGHTSPEED());
598                                 }
599                                 laneBiases.addBias(gtuType, new LaneBias(roadPosition, bias, stickyLanes));
600                             }
601                             setOption(odOptions, ODOptions.getLaneBiasOption(otsNetwork), laneBiases, options, otsNetwork);
602                         }
603                     }
604                 }
605 
606                 // Invoke ODApplier
607                 Map<String, GeneratorObjects> output =
608                         Try.assign(() -> ODApplier.applyOD(otsNetwork, odMatrix, simulator, odOptions),
609                                 XmlParserException.class, "Simulator time should be zero when parsing an OD.");
610 
611                 // Collect generators in output
612                 for (GeneratorObjects generatorObject : output.values())
613                 {
614                     generators.add(generatorObject.getGenerator());
615                 }
616             }
617         }
618 
619         return generators;
620     }
621 
622     /**
623      * Parse the value of a LEVELTIMETYPE that specifies flow (i.e. with 'veh' per time unit).
624      * @param string String; value of LEVELTIMETYPE
625      * @param factor double; total applicable factor on this level
626      * @return Frequency; resulting frequency
627      */
628     private static Frequency parseLevel(final String string, double factor)
629     {
630         return Frequency.valueOf(string.replace("veh", "")).multiplyBy(factor);
631     }
632 
633     /**
634      * Sorts LEVELTIMETYPE in a list by the time value, if any.
635      * @param levelTime List&lt;LEVELTIMETYPE&gt;; sorted list
636      */
637     private static void sortLevelTime(final List<LEVELTIMETYPE> levelTime)
638     {
639         Collections.sort(levelTime, new Comparator<LEVELTIMETYPE>()
640         {
641             /** {@inheritDoc} */
642             @Override
643             public int compare(final LEVELTIMETYPE o1, final LEVELTIMETYPE o2)
644             {
645                 if (o1.getTIME() == null && o2.getTIME() == null)
646                 {
647                     return 0;
648                 }
649                 if (o1.getTIME() == null)
650                 {
651                     return -1;
652                 }
653                 if (o2.getTIME() == null)
654                 {
655                     return 1;
656                 }
657                 return o1.getTIME().compareTo(o2.getTIME());
658             }
659         });
660     }
661 
662     /**
663      * Parse a list of {@code LEVELTIMETYPE} to a {@code TimeVector}.
664      * @param list List&lt;LEVELTIMETYPE&gt;; list of time information
665      * @return TimeVector; time vector
666      * @throws XmlParserException
667      */
668     private final static TimeVector parseTimeVector(final List<LEVELTIMETYPE> list) throws XmlParserException
669     {
670         List<Time> timeList = new ArrayList<>();
671         for (LEVELTIMETYPE time : list)
672         {
673             timeList.add(time.getTIME());
674         }
675         Collections.sort(timeList);
676         return Try.assign(() -> new TimeVector(timeList, StorageType.DENSE), XmlParserException.class,
677                 "Global time has no values.");
678     }
679 
680     /**
681      * Parses a positive factor.
682      * @param factor String; factor in {@code String} format
683      * @return double; factor in {@code double} format
684      * @throws XmlParserException if the factor is not positive
685      */
686     private final static double parsePositiveFactor(final String factor) throws XmlParserException
687     {
688         double factorValue;
689         if (factor.endsWith("%"))
690         {
691             factorValue = Double.parseDouble(factor.substring(0, factor.length() - 1)) / 100.0;
692         }
693         factorValue = Double.parseDouble(factor);
694         Throw.when(factorValue < 0.0, XmlParserException.class, "Factor %d is not positive.", factorValue);
695         return factorValue;
696     }
697 
698     /**
699      * Set option.
700      * @param odOptions ODOptions; OD options to set the option in
701      * @param option Option&lt;T&gt;; option to set
702      * @param value T; value to set the option to
703      * @param options ODOPTIONSITEM; used to set the option on the right level (Link type, origin node, lane
704      * @param otsNetwork OTSRoadNetwork; to get the link type, origin node or lane from
705      * @param <T> option value type
706      */
707     private static <T> void setOption(final ODOptions odOptions, final Option<T> option, final T value,
708             final ODOPTIONSITEM options, final OTSRoadNetwork otsNetwork)
709     {
710         if (value != null)
711         {
712             if (options.getLINKTYPE() != null)
713             {
714                 odOptions.set(otsNetwork.getLinkType(options.getLINKTYPE().getVALUE()), option, value);
715             }
716             else if (options.getORIGIN() != null)
717             {
718                 odOptions.set(otsNetwork.getNode(options.getORIGIN().getVALUE()), option, value);
719             }
720             else if (options.getLANE() != null)
721             {
722                 CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(options.getLANE().getLINK());
723                 odOptions.set((Lane) link.getCrossSectionElement(options.getLANE().getID()), option, value);
724             }
725             else
726             {
727                 odOptions.set(option, value);
728             }
729         }
730     }
731 
732     /**
733      * Returns the ID of a default model, referred if there is a referral specified.
734      * @param model String; model
735      * @param modelIdReferrals String; model ID
736      * @return ID of a model, referred if there is a referral specified
737      */
738     private static String getModelId(final DEFAULTMODEL model, final Map<String, String> modelIdReferrals)
739     {
740         if (model.getMODELIDREFERRAL() != null)
741         {
742             return modelIdReferrals.get(model.getMODELIDREFERRAL());
743         }
744         return model.getID();
745     }
746 
747     /**
748      * Returns the ID of a model, referred if there is a referral specified.
749      * @param model String; model
750      * @param modelIdReferrals String; model ID
751      * @return ID of a model, referred if there is a referral specified
752      */
753     private static String getModelId(final MODEL model, final Map<String, String> modelIdReferrals)
754     {
755         if (model.getMODELIDREFERRAL() != null)
756         {
757             return modelIdReferrals.get(model.getMODELIDREFERRAL());
758         }
759         return model.getID();
760     }
761 
762 }