ODParser.java

  1. package org.opentrafficsim.road.network.factory.xml.parser;

  2. import java.math.BigInteger;
  3. import java.util.ArrayList;
  4. import java.util.Collections;
  5. import java.util.Comparator;
  6. import java.util.LinkedHashMap;
  7. import java.util.LinkedHashSet;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.Set;

  11. import org.djunits.unit.FrequencyUnit;
  12. import org.djunits.unit.TimeUnit;
  13. import org.djunits.value.storage.StorageType;
  14. import org.djunits.value.vdouble.scalar.Acceleration;
  15. import org.djunits.value.vdouble.scalar.Frequency;
  16. import org.djunits.value.vdouble.scalar.Length;
  17. import org.djunits.value.vdouble.scalar.Speed;
  18. import org.djunits.value.vdouble.scalar.Time;
  19. import org.djunits.value.vdouble.vector.FrequencyVector;
  20. import org.djunits.value.vdouble.vector.TimeVector;
  21. import org.djunits.value.vdouble.vector.base.DoubleVector;
  22. import org.djutils.exceptions.Throw;
  23. import org.djutils.exceptions.Try;
  24. import org.djutils.logger.CategoryLogger;
  25. import org.opentrafficsim.base.logger.Cat;
  26. import org.opentrafficsim.core.distributions.Generator;
  27. import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
  28. import org.opentrafficsim.core.gtu.GTUException;
  29. import org.opentrafficsim.core.gtu.GTUType;
  30. import org.opentrafficsim.core.gtu.NestedCache;
  31. import org.opentrafficsim.core.gtu.TemplateGTUType;
  32. import org.opentrafficsim.core.idgenerator.IdGenerator;
  33. import org.opentrafficsim.core.network.Node;
  34. import org.opentrafficsim.core.network.route.Route;
  35. import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBias;
  36. import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
  37. import org.opentrafficsim.road.gtu.generator.GeneratorPositions.RoadPosition;
  38. import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator;
  39. import org.opentrafficsim.road.gtu.generator.MarkovCorrelation;
  40. import org.opentrafficsim.road.gtu.generator.od.DefaultGTUCharacteristicsGeneratorOD.Factory;
  41. import org.opentrafficsim.road.gtu.generator.od.ODApplier;
  42. import org.opentrafficsim.road.gtu.generator.od.ODApplier.GeneratorObjects;
  43. import org.opentrafficsim.road.gtu.generator.od.ODOptions;
  44. import org.opentrafficsim.road.gtu.generator.od.ODOptions.Option;
  45. import org.opentrafficsim.road.gtu.generator.od.StrategicalPlannerFactorySupplierOD;
  46. import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusFactory;
  47. import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLMRSPerceptionFactory;
  48. import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRSFactory;
  49. import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
  50. import org.opentrafficsim.road.gtu.strategical.od.Categorization;
  51. import org.opentrafficsim.road.gtu.strategical.od.Category;
  52. import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
  53. import org.opentrafficsim.road.gtu.strategical.od.ODMatrix;
  54. import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlannerFactory;
  55. import org.opentrafficsim.road.gtu.strategical.route.RouteGeneratorOD;
  56. import org.opentrafficsim.road.network.OTSRoadNetwork;
  57. import org.opentrafficsim.road.network.factory.xml.XmlParserException;
  58. import org.opentrafficsim.road.network.factory.xml.utils.ParseDistribution;
  59. import org.opentrafficsim.road.network.factory.xml.utils.StreamInformation;
  60. import org.opentrafficsim.road.network.factory.xml.utils.Transformer;
  61. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  62. import org.opentrafficsim.road.network.lane.Lane;
  63. import org.opentrafficsim.xml.generated.CATEGORYTYPE;
  64. import org.opentrafficsim.xml.generated.GLOBALTIMETYPE.TIME;
  65. import org.opentrafficsim.xml.generated.GTUTEMPLATE;
  66. import org.opentrafficsim.xml.generated.LEVELTIMETYPE;
  67. import org.opentrafficsim.xml.generated.NETWORKDEMAND;
  68. import org.opentrafficsim.xml.generated.OD;
  69. import org.opentrafficsim.xml.generated.OD.DEMAND;
  70. import org.opentrafficsim.xml.generated.ODOPTIONS;
  71. import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM;
  72. import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM.DEFAULTMODEL;
  73. import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM.LANEBIASES.LANEBIAS;
  74. import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM.MARKOV.STATE;
  75. import org.opentrafficsim.xml.generated.ODOPTIONS.ODOPTIONSITEM.MODEL;

  76. import nl.tudelft.simulation.jstats.streams.StreamInterface;

  77. /**
  78.  * <p>
  79.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  80.  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  81.  * <p>
  82.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Mar 29, 2019 <br>
  83.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  84.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  85.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  86.  */
  87. public final class ODParser
  88. {
  89.     /** */
  90.     private ODParser()
  91.     {
  92.         // static class
  93.     }

  94.     /**
  95.      * Creates generators and returns OD matrices.
  96.      * @param otsNetwork OTSRoadNetwork; network
  97.      * @param simulator OTSSimulatorInterface; simulator
  98.      * @param demands List&lt;NETWORKDEMAND&gt;; demand
  99.      * @param gtuTemplates Map&lt;String, GTUTEMPLATE&gt;; GTU templates
  100.      * @param factories Map&lt;String, LaneBasedStrategicalPlannerFactory&lt;?&gt;&gt;; factories from model parser
  101.      * @param modelIdReferrals Map&lt;String, String&gt;; model id referrals
  102.      * @param streamMap Map&lt;String, StreamInformation&gt;; stream map
  103.      * @return List&lt;LaneBasedGTUGenerator&gt;; generators
  104.      * @throws XmlParserException if the OD contains an inconsistency or error
  105.      */
  106.     @SuppressWarnings("checkstyle:methodlength")
  107.     public static List<LaneBasedGTUGenerator> parseDemand(final OTSRoadNetwork otsNetwork,
  108.             final OTSSimulatorInterface simulator, final List<NETWORKDEMAND> demands,
  109.             final Map<String, GTUTEMPLATE> gtuTemplates, final Map<String, LaneBasedStrategicalPlannerFactory<?>> factories,
  110.             final Map<String, String> modelIdReferrals, final Map<String, StreamInformation> streamMap)
  111.             throws XmlParserException
  112.     {
  113.         List<LaneBasedGTUGenerator> generators = new ArrayList<>();

  114.         IdGenerator idGenerator = new IdGenerator("");

  115.         for (NETWORKDEMAND subDemand : demands)
  116.         {
  117.             // Collect options
  118.             Map<String, ODOPTIONS> odOptionsMap = new LinkedHashMap<>();
  119.             for (ODOPTIONS odOptions : subDemand.getODOPTIONS())
  120.             {
  121.                 odOptionsMap.put(odOptions.getID(), odOptions);
  122.             }
  123.             List<OD> ods = subDemand.getOD();
  124.             for (OD od : ods)
  125.             {

  126.                 // ID
  127.                 String id = od.getID();

  128.                 // Origins and destinations, retrieve them from demand items
  129.                 List<Node> origins = new ArrayList<>();
  130.                 List<Node> destinations = new ArrayList<>();
  131.                 for (DEMAND demand : od.getDEMAND())
  132.                 {
  133.                     if (!origins.contains(otsNetwork.getNode(demand.getORIGIN())))
  134.                     {
  135.                         Node originNode = otsNetwork.getNode(demand.getORIGIN());
  136.                         if (null == originNode)
  137.                         {
  138.                             CategoryLogger.filter(Cat.PARSER).trace("Parse demand: cannot find origin {}", demand.getORIGIN());
  139.                         }
  140.                         else
  141.                         {
  142.                             // TODO: will skipping origins that are not in the network cause problems later on?
  143.                             origins.add(originNode);
  144.                         }
  145.                     }
  146.                     if (!destinations.contains(otsNetwork.getNode(demand.getDESTINATION())))
  147.                     {
  148.                         Node destinationNode = otsNetwork.getNode(demand.getDESTINATION());
  149.                         if (null == destinationNode)
  150.                         {
  151.                             CategoryLogger.filter(Cat.PARSER).trace("Parse demand: cannot find destination {}",
  152.                                     demand.getDESTINATION());
  153.                         }
  154.                         else
  155.                         {
  156.                             // TODO: will skipping origins that are not in the network cause problems later on?
  157.                             destinations.add(destinationNode);
  158.                         }
  159.                     }
  160.                 }

  161.                 // Create categorization
  162.                 Categorization categorization;
  163.                 Map<String, Category> categories = new LinkedHashMap<>();
  164.                 Map<String, Double> categoryFactors = new LinkedHashMap<>();
  165.                 if (od.getCATEGORY() == null || od.getCATEGORY().isEmpty())
  166.                 {
  167.                     categorization = Categorization.UNCATEGORIZED;
  168.                 }
  169.                 else
  170.                 {
  171.                     List<Class<?>> categoryClasses = new ArrayList<>();
  172.                     if (od.getCATEGORY().get(0).getGTUTYPE() != null)
  173.                     {
  174.                         categoryClasses.add(GTUType.class);
  175.                     }
  176.                     if (od.getCATEGORY().get(0).getROUTE() != null)
  177.                     {
  178.                         categoryClasses.add(Route.class);
  179.                     }
  180.                     if (od.getCATEGORY().get(0).getLANE() != null)
  181.                     {
  182.                         categoryClasses.add(Lane.class);
  183.                     }
  184.                     if (categoryClasses.isEmpty())
  185.                     {
  186.                         // XML uses categories, but these define nothing
  187.                         categorization = Categorization.UNCATEGORIZED;
  188.                     }
  189.                     else
  190.                     {
  191.                         Class<?> clazz = categoryClasses.get(0);
  192.                         categoryClasses.remove(0);
  193.                         categorization = new Categorization("", clazz, categoryClasses.toArray(new Class[0]));
  194.                     }
  195.                     // create categories and check that all categories comply with the categorization
  196.                     for (CATEGORYTYPE category : od.getCATEGORY())
  197.                     {
  198.                         Throw.when(
  199.                                 (categorization.entails(GTUType.class) && category.getGTUTYPE() == null)
  200.                                         || (!categorization.entails(GTUType.class) && category.getGTUTYPE() != null),
  201.                                 XmlParserException.class, "Categories are inconsistent concerning GTUType.");
  202.                         Throw.when(
  203.                                 (categorization.entails(Route.class) && category.getROUTE() == null)
  204.                                         || (!categorization.entails(Route.class) && category.getROUTE() != null),
  205.                                 XmlParserException.class, "Categories are inconsistent concerning Route.");
  206.                         Throw.when(
  207.                                 (categorization.entails(Lane.class) && category.getLANE() == null)
  208.                                         || (!categorization.entails(Lane.class) && category.getLANE() != null),
  209.                                 XmlParserException.class, "Categories are inconsistent concerning Lane.");
  210.                         List<Object> objects = new ArrayList<>();
  211.                         if (categorization.entails(GTUType.class))
  212.                         {
  213.                             objects.add(otsNetwork.getGtuType(category.getGTUTYPE()));
  214.                         }
  215.                         if (categorization.entails(Route.class))
  216.                         {
  217.                             objects.add(otsNetwork.getRoute(category.getROUTE()));
  218.                         }
  219.                         if (categorization.entails(Lane.class))
  220.                         {
  221.                             CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(category.getLANE().getLINK());
  222.                             Lane lane = (Lane) link.getCrossSectionElement(category.getLANE().getID());
  223.                             objects.add(lane);
  224.                         }
  225.                         categories.put(category.getID(), new Category(categorization, objects.get(0),
  226.                                 objects.subList(1, objects.size()).toArray(new Object[objects.size() - 1])));
  227.                         categoryFactors.put(category.getID(), parsePositiveFactor(category.getFACTOR()));
  228.                     }
  229.                 }

  230.                 // Global time vector
  231.                 TimeVector globalTimeVector = null;
  232.                 if (od.getGLOBALTIME() != null)
  233.                 {
  234.                     List<Time> timeList = new ArrayList<>();
  235.                     for (TIME time : od.getGLOBALTIME().getTIME())
  236.                     {
  237.                         timeList.add(time.getVALUE());
  238.                     }
  239.                     Collections.sort(timeList);
  240.                     globalTimeVector =
  241.                             Try.assign(() -> DoubleVector.instantiateList(timeList, TimeUnit.DEFAULT, StorageType.DENSE),
  242.                                     XmlParserException.class, "Global time has no values.");
  243.                 }

  244.                 // Global interpolation
  245.                 Interpolation globalInterpolation =
  246.                         od.getGLOBALINTERPOLATION().equals("LINEAR") ? Interpolation.LINEAR : Interpolation.STEPWISE;

  247.                 // Global factor
  248.                 double globalFactor = parsePositiveFactor(od.getGLOBALFACTOR());

  249.                 // Create the OD matrix
  250.                 ODMatrix odMatrix =
  251.                         new ODMatrix(id, origins, destinations, categorization, globalTimeVector, globalInterpolation);

  252.                 // Add demand
  253.                 NestedCache<Set<DEMAND>> demandPerOD = new NestedCache<>(Node.class, Node.class);
  254.                 for (DEMAND demand : od.getDEMAND())
  255.                 {
  256.                     Node origin = otsNetwork.getNode(demand.getORIGIN());
  257.                     Node destination = otsNetwork.getNode(demand.getDESTINATION());
  258.                     demandPerOD.getValue(() -> new LinkedHashSet<>(), origin, destination).add(demand);
  259.                 }
  260.                 for (Object o : demandPerOD.getKeys())
  261.                 {
  262.                     NestedCache<Set<DEMAND>> demandPerD = demandPerOD.getChild(o);
  263.                     for (Object d : demandPerD.getKeys())
  264.                     {
  265.                         Set<DEMAND> set = demandPerD.getValue(d);
  266.                         Node origin = (Node) o;
  267.                         Node destination = (Node) d;
  268.                         Throw.when(categorization.equals(Categorization.UNCATEGORIZED) && set.size() > 1,
  269.                                 XmlParserException.class,
  270.                                 "Multiple DEMAND tags define demand from %s to %s in uncategorized demand.", origin.getId(),
  271.                                 destination.getId());

  272.                         // Find main demand, that may be split among other DEMAND tags between the same origin and destination
  273.                         DEMAND main = null;
  274.                         if (!categorization.equals(Categorization.UNCATEGORIZED))
  275.                         {
  276.                             for (DEMAND demand : set)
  277.                             {
  278.                                 if (demand.getCATEGORY() == null)
  279.                                 {
  280.                                     Throw.when(main != null, XmlParserException.class,
  281.                                             "Multiple DEMAND tags define main demand from %s to %s.", origin.getId(),
  282.                                             destination.getId());
  283.                                     Throw.when(set.size() == 1, XmlParserException.class,
  284.                                             "Categorized demand from %s to %s has single DEMAND, and without category.",
  285.                                             origin.getId(), destination.getId());
  286.                                     main = demand;
  287.                                 }
  288.                             }
  289.                         }

  290.                         // Add demand per tag
  291.                         for (DEMAND demand : set)
  292.                         {
  293.                             // Skip main demand, it is split among other tags
  294.                             if (demand.equals(main))
  295.                             {
  296.                                 continue;
  297.                             }

  298.                             // TimeVector: demand > main demand > global
  299.                             List<LEVELTIMETYPE> timeTags =
  300.                                     demand.getLEVEL() == null || demand.getLEVEL().get(0).getTIME() == null
  301.                                             ? (main == null || main.getLEVEL() == null
  302.                                                     || main.getLEVEL().get(0).getTIME() == null ? null : main.getLEVEL())
  303.                                             : demand.getLEVEL();
  304.                             TimeVector timeVector = timeTags == null ? globalTimeVector : parseTimeVector(timeTags);

  305.                             // Interpolation: demand > main demand > global
  306.                             // TODO: LINEAR follows when only global STEPWISE is defined
  307.                             String interpolationString = demand.getINTERPOLATION() == null
  308.                                     ? (main == null || main.getINTERPOLATION() == null ? null : main.getINTERPOLATION())
  309.                                     : demand.getINTERPOLATION();
  310.                             Interpolation interpolation = interpolationString == null ? globalInterpolation
  311.                                     : interpolationString.equals("LINEAR") ? Interpolation.LINEAR : Interpolation.STEPWISE;

  312.                             // Category
  313.                             Category category = categorization.equals(Categorization.UNCATEGORIZED) ? Category.UNCATEGORIZED
  314.                                     : categories.get(demand.getCATEGORY());

  315.                             // Factor
  316.                             double factor = globalFactor;
  317.                             factor = main == null ? factor : factor * parsePositiveFactor(main.getFACTOR());
  318.                             factor *= parsePositiveFactor(demand.getFACTOR());

  319.                             // Figure out where the base demand, and optional factors are
  320.                             Frequency[] demandRaw = new Frequency[timeVector.size()];
  321.                             List<LEVELTIMETYPE> baseDemand;
  322.                             List<LEVELTIMETYPE> factors = null;
  323.                             if (demand.getLEVEL() == null)
  324.                             {
  325.                                 // this demand specified no levels, use main demand
  326.                                 baseDemand = main.getLEVEL();
  327.                             }
  328.                             else if (demand.getLEVEL().get(0).getValue().contains("veh"))
  329.                             {
  330.                                 // this demand specifies levels
  331.                                 baseDemand = demand.getLEVEL();
  332.                             }
  333.                             else
  334.                             {
  335.                                 // this demand specifies factors on the main demand
  336.                                 baseDemand = main.getLEVEL();
  337.                                 factors = demand.getLEVEL();
  338.                             }
  339.                             // sort
  340.                             sortLevelTime(baseDemand);
  341.                             if (factors != null)
  342.                             {
  343.                                 sortLevelTime(factors);
  344.                             }
  345.                             // fill array, include factors
  346.                             for (int i = 0; i < baseDemand.size(); i++)
  347.                             {
  348.                                 Throw.when(
  349.                                         baseDemand.get(i).getTIME() != null && factors != null
  350.                                                 && factors.get(i).getTIME() != null
  351.                                                 && !baseDemand.get(i).getTIME().eq(factors.get(i).getTIME()),
  352.                                         XmlParserException.class,
  353.                                         "Demand from %s to %s is specified with factors that have different time from the base demand.",
  354.                                         origin, destination);
  355.                                 demandRaw[i] = parseLevel(baseDemand.get(i).getValue(),
  356.                                         factor * (factors == null ? 1.0 : parsePositiveFactor(factors.get(i).getValue())));
  357.                             }
  358.                             FrequencyVector demandVector =
  359.                                     Try.assign(() -> DoubleVector.instantiate(demandRaw, FrequencyUnit.SI, StorageType.DENSE),
  360.                                             XmlParserException.class, "Unexpected empty demand.");

  361.                             // Finally, add the demand
  362.                             odMatrix.putDemandVector(origin, destination, category, demandVector, timeVector, interpolation);
  363.                         }

  364.                     }
  365.                 }

  366.                 // OD Options
  367.                 ODOptions odOptions =
  368.                         new ODOptions().set(ODOptions.GTU_ID, idGenerator).set(ODOptions.NO_LC_DIST, Length.instantiateSI(1.0));
  369.                 // templates
  370.                 Set<TemplateGTUType> templates = new LinkedHashSet<>();
  371.                 for (GTUTEMPLATE template : gtuTemplates.values())
  372.                 {
  373.                     GTUType gtuType = otsNetwork.getGtuType(template.getGTUTYPE());
  374.                     Generator<Length> lengthGenerator = ParseDistribution.parseLengthDist(streamMap, template.getLENGTHDIST());
  375.                     Generator<Length> widthGenerator = ParseDistribution.parseLengthDist(streamMap, template.getWIDTHDIST());
  376.                     Generator<Speed> maximumSpeedGenerator =
  377.                             ParseDistribution.parseSpeedDist(streamMap, template.getMAXSPEEDDIST());
  378.                     if (template.getMAXACCELERATIONDIST() == null || template.getMAXDECELERATIONDIST() == null)
  379.                     {
  380.                         templates.add(new TemplateGTUType(gtuType, lengthGenerator, widthGenerator, maximumSpeedGenerator));
  381.                     }
  382.                     else
  383.                     {
  384.                         Generator<Acceleration> maxAccelerationGenerator =
  385.                                 ParseDistribution.parseAccelerationDist(streamMap, template.getMAXACCELERATIONDIST());
  386.                         Generator<Acceleration> maxDecelerationGenerator =
  387.                                 ParseDistribution.parseAccelerationDist(streamMap, template.getMAXDECELERATIONDIST());
  388.                         templates.add(new TemplateGTUType(gtuType, lengthGenerator, widthGenerator, maximumSpeedGenerator,
  389.                                 maxAccelerationGenerator, maxDecelerationGenerator));
  390.                     }
  391.                 }
  392.                 // default global option to integrate defined templates
  393.                 Factory factory = new Factory(); // DefaultGTUCharacteristicsGeneratorOD factory
  394.                 factory.setTemplates(templates);
  395.                 odOptions.set(ODOptions.GTU_TYPE, factory.create());
  396.                 // other options
  397.                 if (od.getOPTIONS() != null)
  398.                 {
  399.                     Throw.when(!odOptionsMap.containsKey(od.getOPTIONS()), XmlParserException.class,
  400.                             "OD options of id od.getOPTIONS() not defined.");
  401.                     for (ODOPTIONSITEM options : odOptionsMap.get(od.getOPTIONS()).getODOPTIONSITEM())
  402.                     {
  403.                         /*
  404.                          * The current 'options' is valid within a single context, i.e. global, link type, origin or lane. All
  405.                          * option values are set in odOptions for that context, in the current loop. For the model factories an
  406.                          * implementation of StrategicalPlannerFactorySupplierOD is created that responds to the GTU type, and
  407.                          * selects a factory assigned to that GTU type within the context. Or, the default factory in the
  408.                          * context is used. Or finally, a default LMRS. If no model factory is specified in the context (nor a
  409.                          * higher context), no option value is set and ODOptions itself returns a default LMRS factory.
  410.                          */

  411.                         // GTU type (model)
  412.                         if (options.getDEFAULTMODEL() != null || (options.getMODEL() != null && !options.getMODEL().isEmpty()))
  413.                         {
  414.                             LaneBasedStrategicalPlannerFactory<?> defaultFactory;
  415.                             if (options.getDEFAULTMODEL() != null)
  416.                             {
  417.                                 // TODO: model id referral
  418.                                 String modelId = ODParser.getModelId(options.getDEFAULTMODEL(), modelIdReferrals);
  419.                                 Throw.when(!factories.containsKey(modelId), XmlParserException.class,
  420.                                         "OD option DEFAULTMODEL refers to a non-existent model with ID %s.", modelId);
  421.                                 defaultFactory = factories.get(modelId);
  422.                             }
  423.                             else
  424.                             {
  425.                                 defaultFactory = null;
  426.                             }
  427.                             // compose map that couples GTU types to factories through MODEL ID's
  428.                             final Map<GTUType, LaneBasedStrategicalPlannerFactory<?>> gtuTypeFactoryMap = new LinkedHashMap<>();
  429.                             if (options.getMODEL() != null)
  430.                             {
  431.                                 for (MODEL model : options.getMODEL())
  432.                                 {
  433.                                     GTUType gtuType = otsNetwork.getGtuType(model.getGTUTYPE());
  434.                                     Throw.when(!factories.containsKey(model.getID()), XmlParserException.class,
  435.                                             "OD option MODEL refers to a non existent-model with ID %s.", model.getID());
  436.                                     gtuTypeFactoryMap.put(gtuType, factories.get(getModelId(model, modelIdReferrals)));
  437.                                 }
  438.                             }
  439.                             factory = new Factory(); // DefaultGTUCharacteristicsGeneratorOD factory
  440.                             factory.setTemplates(templates);
  441.                             factory.setFactorySupplier(new StrategicalPlannerFactorySupplierOD()
  442.                             {
  443.                                 /** {@inheritDoc} */
  444.                                 @Override
  445.                                 public LaneBasedStrategicalPlannerFactory<?> getFactory(final Node origin,
  446.                                         final Node destination, final Category category, final StreamInterface randomStream)
  447.                                         throws GTUException
  448.                                 {
  449.                                     if (category.getCategorization().entails(GTUType.class))
  450.                                     {
  451.                                         LaneBasedStrategicalPlannerFactory<?> strategicalPlannerFactory =
  452.                                                 gtuTypeFactoryMap.get(category.get(GTUType.class));
  453.                                         if (strategicalPlannerFactory != null)
  454.                                         {
  455.                                             // a model factory for this GTU type is specified
  456.                                             return strategicalPlannerFactory;
  457.                                         }
  458.                                     }
  459.                                     if (defaultFactory != null)
  460.                                     {
  461.                                         // a default model factory is specified
  462.                                         return defaultFactory;
  463.                                     }
  464.                                     // no model factory specified, return a default LMRS factory
  465.                                     // TODO: LMRSFactory can receive a parameter factory, but how to define those parameters in
  466.                                     // XML?
  467.                                     return new LaneBasedStrategicalRoutePlannerFactory(
  468.                                             new LMRSFactory(new IDMPlusFactory(randomStream),
  469.                                                     new DefaultLMRSPerceptionFactory()),
  470.                                             RouteGeneratorOD.getDefaultRouteSupplier(randomStream));
  471.                                 }
  472.                             });
  473.                             setOption(odOptions, ODOptions.GTU_TYPE, factory.create(), options, otsNetwork);
  474.                         }
  475.                         // no lc
  476.                         setOption(odOptions, ODOptions.NO_LC_DIST, options.getNOLANECHANGE(), options, otsNetwork);
  477.                         // room checker
  478.                         setOption(odOptions, ODOptions.ROOM_CHECKER, Transformer.parseRoomChecker(options.getROOMCHECKER()),
  479.                                 options, otsNetwork);
  480.                         // headway distribution
  481.                         try
  482.                         {
  483.                             setOption(odOptions, ODOptions.HEADWAY_DIST,
  484.                                     Transformer.parseHeadwayDistribution(options.getHEADWAYDIST()), options, otsNetwork);
  485.                         }
  486.                         catch (NoSuchFieldException | IllegalAccessException exception)
  487.                         {
  488.                             throw new XmlParserException(exception);
  489.                         }
  490.                         // markov
  491.                         if (options.getMARKOV() != null)
  492.                         {
  493.                             Throw.when(!categorization.entails(GTUType.class), XmlParserException.class,
  494.                                     "The OD option MARKOV can only be used if GTUType is in the CATEGORY's.");
  495.                             Throw.when(!categorization.entails(Lane.class) && options.getLANE() != null,
  496.                                     XmlParserException.class,
  497.                                     "Markov chains at lane level are not used if Lane's are not in the CATEGORY's.");
  498.                             MarkovCorrelation<GTUType, Frequency> markov = new MarkovCorrelation<>();
  499.                             for (STATE state : options.getMARKOV().getSTATE())
  500.                             {
  501.                                 GTUType gtuType = otsNetwork.getGtuType(state.getGTUTYPE());
  502.                                 double correlation = state.getCORRELATION();
  503.                                 if (state.getPARENT() == null)
  504.                                 {
  505.                                     markov.addState(gtuType, correlation);
  506.                                 }
  507.                                 else
  508.                                 {
  509.                                     GTUType parentType = otsNetwork.getGtuType(state.getPARENT());
  510.                                     markov.addState(parentType, gtuType, correlation);
  511.                                 }
  512.                             }
  513.                             setOption(odOptions, ODOptions.MARKOV, markov, options, otsNetwork);
  514.                         }
  515.                         // lane biases
  516.                         if (options.getLANEBIASES() != null)
  517.                         {
  518.                             LaneBiases laneBiases = new LaneBiases();
  519.                             for (LANEBIAS laneBias : options.getLANEBIASES().getLANEBIAS())
  520.                             {
  521.                                 GTUType gtuType = otsNetwork.getGtuType(laneBias.getGTUTYPE());
  522.                                 double bias = laneBias.getBIAS();
  523.                                 int stickyLanes;
  524.                                 if (laneBias.getSTICKYLANES() == null)
  525.                                 {
  526.                                     stickyLanes = Integer.MAX_VALUE;
  527.                                 }
  528.                                 else
  529.                                 {
  530.                                     if (laneBias.getSTICKYLANES().compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0)
  531.                                     {
  532.                                         stickyLanes = Integer.MAX_VALUE;
  533.                                     }
  534.                                     else
  535.                                     {
  536.                                         stickyLanes = laneBias.getSTICKYLANES().intValue();
  537.                                     }
  538.                                 }
  539.                                 RoadPosition roadPosition;
  540.                                 if (laneBias.getFROMRIGHT() != null)
  541.                                 {
  542.                                     roadPosition = new RoadPosition.ByValue(laneBias.getFROMRIGHT());
  543.                                 }
  544.                                 else if (laneBias.getFROMLEFT() != null)
  545.                                 {
  546.                                     roadPosition = new RoadPosition.ByValue(1.0 - laneBias.getFROMLEFT());
  547.                                 }
  548.                                 else
  549.                                 {
  550.                                     roadPosition = new RoadPosition.BySpeed(laneBias.getLEFTSPEED(), laneBias.getRIGHTSPEED());
  551.                                 }
  552.                                 laneBiases.addBias(gtuType, new LaneBias(roadPosition, bias, stickyLanes));
  553.                             }
  554.                             setOption(odOptions, ODOptions.getLaneBiasOption(otsNetwork), laneBiases, options, otsNetwork);
  555.                         }
  556.                     }
  557.                 }

  558.                 // Invoke ODApplier
  559.                 Map<String, GeneratorObjects> output =
  560.                         Try.assign(() -> ODApplier.applyOD(otsNetwork, odMatrix, simulator, odOptions),
  561.                                 XmlParserException.class, "Simulator time should be zero when parsing an OD.");

  562.                 // Collect generators in output
  563.                 for (GeneratorObjects generatorObject : output.values())
  564.                 {
  565.                     generators.add(generatorObject.getGenerator());
  566.                 }
  567.             }
  568.         }

  569.         return generators;
  570.     }

  571.     /**
  572.      * Parse the value of a LEVELTIMETYPE that specifies flow (i.e. with 'veh' per time unit).
  573.      * @param string String; value of LEVELTIMETYPE
  574.      * @param factor double; total applicable factor on this level
  575.      * @return Frequency; resulting frequency
  576.      */
  577.     private static Frequency parseLevel(final String string, final double factor)
  578.     {
  579.         return Frequency.valueOf(string.replace("veh", "")).times(factor);
  580.     }

  581.     /**
  582.      * Sorts LEVELTIMETYPE in a list by the time value, if any.
  583.      * @param levelTime List&lt;LEVELTIMETYPE&gt;; sorted list
  584.      */
  585.     private static void sortLevelTime(final List<LEVELTIMETYPE> levelTime)
  586.     {
  587.         Collections.sort(levelTime, new Comparator<LEVELTIMETYPE>()
  588.         {
  589.             /** {@inheritDoc} */
  590.             @Override
  591.             public int compare(final LEVELTIMETYPE o1, final LEVELTIMETYPE o2)
  592.             {
  593.                 if (o1.getTIME() == null && o2.getTIME() == null)
  594.                 {
  595.                     return 0;
  596.                 }
  597.                 if (o1.getTIME() == null)
  598.                 {
  599.                     return -1;
  600.                 }
  601.                 if (o2.getTIME() == null)
  602.                 {
  603.                     return 1;
  604.                 }
  605.                 return o1.getTIME().compareTo(o2.getTIME());
  606.             }
  607.         });
  608.     }

  609.     /**
  610.      * Parse a list of {@code LEVELTIMETYPE} to a {@code TimeVector}.
  611.      * @param list List&lt;LEVELTIMETYPE&gt;; list of time information
  612.      * @return TimeVector; time vector
  613.      * @throws XmlParserException if global time has no values
  614.      */
  615.     private static TimeVector parseTimeVector(final List<LEVELTIMETYPE> list) throws XmlParserException
  616.     {
  617.         List<Time> timeList = new ArrayList<>();
  618.         for (LEVELTIMETYPE time : list)
  619.         {
  620.             timeList.add(time.getTIME());
  621.         }
  622.         Collections.sort(timeList);
  623.         return Try.assign(() -> DoubleVector.instantiateList(timeList, TimeUnit.DEFAULT, StorageType.DENSE),
  624.                 XmlParserException.class, "Global time has no values.");
  625.     }

  626.     /**
  627.      * Parses a positive factor.
  628.      * @param factor String; factor in {@code String} format
  629.      * @return double; factor in {@code double} format
  630.      * @throws XmlParserException if the factor is not positive
  631.      */
  632.     private static double parsePositiveFactor(final String factor) throws XmlParserException
  633.     {
  634.         double factorValue;
  635.         if (factor.endsWith("%"))
  636.         {
  637.             factorValue = Double.parseDouble(factor.substring(0, factor.length() - 1)) / 100.0;
  638.         }
  639.         factorValue = Double.parseDouble(factor);
  640.         Throw.when(factorValue < 0.0, XmlParserException.class, "Factor %d is not positive.", factorValue);
  641.         return factorValue;
  642.     }

  643.     /**
  644.      * Set option.
  645.      * @param odOptions ODOptions; OD options to set the option in
  646.      * @param option Option&lt;T&gt;; option to set
  647.      * @param value T; value to set the option to
  648.      * @param options ODOPTIONSITEM; used to set the option on the right level (Link type, origin node, lane
  649.      * @param otsNetwork OTSRoadNetwork; to get the link type, origin node or lane from
  650.      * @param <T> option value type
  651.      */
  652.     private static <T> void setOption(final ODOptions odOptions, final Option<T> option, final T value,
  653.             final ODOPTIONSITEM options, final OTSRoadNetwork otsNetwork)
  654.     {
  655.         if (value != null)
  656.         {
  657.             if (options.getLINKTYPE() != null)
  658.             {
  659.                 odOptions.set(otsNetwork.getLinkType(options.getLINKTYPE().getVALUE()), option, value);
  660.             }
  661.             else if (options.getORIGIN() != null)
  662.             {
  663.                 odOptions.set(otsNetwork.getNode(options.getORIGIN().getVALUE()), option, value);
  664.             }
  665.             else if (options.getLANE() != null)
  666.             {
  667.                 CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(options.getLANE().getLINK());
  668.                 odOptions.set((Lane) link.getCrossSectionElement(options.getLANE().getID()), option, value);
  669.             }
  670.             else
  671.             {
  672.                 odOptions.set(option, value);
  673.             }
  674.         }
  675.     }

  676.     /**
  677.      * Returns the ID of a default model, referred if there is a referral specified.
  678.      * @param model String; model
  679.      * @param modelIdReferrals String; model ID
  680.      * @return ID of a model, referred if there is a referral specified
  681.      */
  682.     private static String getModelId(final DEFAULTMODEL model, final Map<String, String> modelIdReferrals)
  683.     {
  684.         if (model.getMODELIDREFERRAL() != null)
  685.         {
  686.             return modelIdReferrals.get(model.getMODELIDREFERRAL());
  687.         }
  688.         return model.getID();
  689.     }

  690.     /**
  691.      * Returns the ID of a model, referred if there is a referral specified.
  692.      * @param model String; model
  693.      * @param modelIdReferrals String; model ID
  694.      * @return ID of a model, referred if there is a referral specified
  695.      */
  696.     private static String getModelId(final MODEL model, final Map<String, String> modelIdReferrals)
  697.     {
  698.         if (model.getMODELIDREFERRAL() != null)
  699.         {
  700.             return modelIdReferrals.get(model.getMODELIDREFERRAL());
  701.         }
  702.         return model.getID();
  703.     }

  704. }