OdParser.java

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

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

  10. import org.djunits.unit.FrequencyUnit;
  11. import org.djunits.unit.TimeUnit;
  12. import org.djunits.value.vdouble.scalar.Acceleration;
  13. import org.djunits.value.vdouble.scalar.Frequency;
  14. import org.djunits.value.vdouble.scalar.Length;
  15. import org.djunits.value.vdouble.scalar.Speed;
  16. import org.djunits.value.vdouble.scalar.Time;
  17. import org.djunits.value.vdouble.vector.FrequencyVector;
  18. import org.djunits.value.vdouble.vector.TimeVector;
  19. import org.djutils.eval.Eval;
  20. import org.djutils.exceptions.Throw;
  21. import org.djutils.exceptions.Try;
  22. import org.djutils.multikeymap.MultiKeyMap;
  23. import org.opentrafficsim.core.definitions.Definitions;
  24. import org.opentrafficsim.core.distributions.Generator;
  25. import org.opentrafficsim.core.gtu.GtuException;
  26. import org.opentrafficsim.core.gtu.GtuTemplate;
  27. import org.opentrafficsim.core.gtu.GtuType;
  28. import org.opentrafficsim.core.idgenerator.IdGenerator;
  29. import org.opentrafficsim.core.network.LinkType;
  30. import org.opentrafficsim.core.network.Node;
  31. import org.opentrafficsim.core.network.route.Route;
  32. import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBias;
  33. import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
  34. import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator;
  35. import org.opentrafficsim.road.gtu.generator.MarkovCorrelation;
  36. import org.opentrafficsim.road.gtu.generator.characteristics.DefaultLaneBasedGtuCharacteristicsGeneratorOd;
  37. import org.opentrafficsim.road.gtu.generator.characteristics.DefaultLaneBasedGtuCharacteristicsGeneratorOd.Factory;
  38. import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
  39. import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
  40. import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
  41. import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalRoutePlannerFactory;
  42. import org.opentrafficsim.road.network.RoadNetwork;
  43. import org.opentrafficsim.road.network.factory.xml.XmlParserException;
  44. import org.opentrafficsim.road.network.factory.xml.utils.ParseDistribution;
  45. import org.opentrafficsim.road.network.factory.xml.utils.ParseUtil;
  46. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  47. import org.opentrafficsim.road.network.lane.Lane;
  48. import org.opentrafficsim.road.network.lane.object.detector.DetectorType;
  49. import org.opentrafficsim.road.od.Categorization;
  50. import org.opentrafficsim.road.od.Category;
  51. import org.opentrafficsim.road.od.Interpolation;
  52. import org.opentrafficsim.road.od.OdApplier;
  53. import org.opentrafficsim.road.od.OdApplier.GeneratorObjects;
  54. import org.opentrafficsim.road.od.OdMatrix;
  55. import org.opentrafficsim.road.od.OdOptions;
  56. import org.opentrafficsim.road.od.OdOptions.Option;
  57. import org.opentrafficsim.xml.bindings.PositiveFactorAdapter;
  58. import org.opentrafficsim.xml.generated.CategoryType;
  59. import org.opentrafficsim.xml.generated.Demand;
  60. import org.opentrafficsim.xml.generated.LevelTimeType;
  61. import org.opentrafficsim.xml.generated.Od;
  62. import org.opentrafficsim.xml.generated.Od.Cell;
  63. import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem;
  64. import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem.DefaultModel;
  65. import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem.LaneBiases.DefinedLaneBias;
  66. import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem.Markov.State;
  67. import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem.Model;

  68. import nl.tudelft.simulation.dsol.experiment.StreamInformation;
  69. import nl.tudelft.simulation.jstats.streams.StreamInterface;

  70. /**
  71.  * This utility creates GTU generators from an OD matrix.
  72.  * <p>
  73.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  74.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  75.  * </p>
  76.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  77.  * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  78.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  79.  */
  80. public final class OdParser
  81. {
  82.     /** */
  83.     private OdParser()
  84.     {
  85.         // static class
  86.     }

  87.     /**
  88.      * Creates generators and returns OD matrices.
  89.      * @param otsNetwork RoadNetwork; network
  90.      * @param definitions Definitions; parsed definitions.
  91.      * @param demand Demand; demand
  92.      * @param gtuTemplates Map&lt;String, org.opentrafficsim.xml.generated.GtuTemplate&gt;; GTU templates
  93.      * @param definedLaneBiases Map&lt;String, LaneBias&lt;?&gt;&gt;; defined lane biases
  94.      * @param factories Map&lt;String, LaneBasedStrategicalPlannerFactory&lt;?&gt;&gt;; factories from model parser
  95.      * @param modelIdReferrals Map&lt;String, String&gt;; model id referrals
  96.      * @param streamMap Map&lt;String, StreamInformation&gt;; stream map
  97.      * @param eval Eval; expression evaluator.
  98.      * @return List&lt;LaneBasedGtuGenerator&gt;; generators
  99.      * @throws XmlParserException if the OD contains an inconsistency or error
  100.      */
  101.     public static List<LaneBasedGtuGenerator> parseDemand(final RoadNetwork otsNetwork, final Definitions definitions,
  102.             final Demand demand, final Map<String, org.opentrafficsim.xml.generated.GtuTemplate> gtuTemplates,
  103.             final Map<String, LaneBias> definedLaneBiases, final Map<String, LaneBasedStrategicalPlannerFactory<?>> factories,
  104.             final Map<String, String> modelIdReferrals, final StreamInformation streamMap, final Eval eval)
  105.             throws XmlParserException
  106.     {
  107.         List<LaneBasedGtuGenerator> generators = new ArrayList<>();

  108.         // Collect options
  109.         Map<String, org.opentrafficsim.xml.generated.OdOptions> odOptionsMap = new LinkedHashMap<>();
  110.         for (org.opentrafficsim.xml.generated.OdOptions odOptions : demand.getOdOptions())
  111.         {
  112.             odOptionsMap.put(odOptions.getId(), odOptions);
  113.         }

  114.         for (Od od : demand.getOd())
  115.         {
  116.             // Origins and destinations, retrieve them from demand items
  117.             List<Node> origins = new ArrayList<>();
  118.             List<Node> destinations = new ArrayList<>();
  119.             for (Cell cell : od.getCell())
  120.             {
  121.                 String originId = cell.getOrigin().get(eval);
  122.                 if (!origins.contains(otsNetwork.getNode(originId)))
  123.                 {
  124.                     Node originNode = otsNetwork.getNode(originId);
  125.                     Throw.whenNull(originNode, "Parse demand: cannot find origin %s", originId);
  126.                     origins.add(originNode);
  127.                 }
  128.                 String destinationId = cell.getDestination().get(eval);
  129.                 if (!destinations.contains(otsNetwork.getNode(destinationId)))
  130.                 {
  131.                     Node destinationNode = otsNetwork.getNode(destinationId);
  132.                     Throw.whenNull(destinationNode, "Parse demand: cannot find destination %s", destinationId);
  133.                     destinations.add(destinationNode);
  134.                 }
  135.             }

  136.             // Create categorization
  137.             Map<String, Category> categories = new LinkedHashMap<>();
  138.             Categorization categorization = parseCategories(otsNetwork, definitions, od, categories, eval);

  139.             // Global time vector
  140.             TimeVector globalTimeVector = null;
  141.             if (od.getGlobalTime() != null)
  142.             {
  143.                 List<Time> timeList = new ArrayList<>();
  144.                 for (org.opentrafficsim.xml.generated.GlobalTimeType.Time time : od.getGlobalTime().getTime())
  145.                 {
  146.                     timeList.add(time.getValue().get(eval));
  147.                 }
  148.                 Collections.sort(timeList);
  149.                 globalTimeVector = Try.assign(() -> new TimeVector(timeList, TimeUnit.DEFAULT), XmlParserException.class,
  150.                         "Global time has no values.");
  151.             }

  152.             Interpolation globalInterpolation = od.getGlobalInterpolation().get(eval);
  153.             double globalFactor = od.getGlobalFactor().get(eval);

  154.             // Create the OD matrix
  155.             OdMatrix odMatrix =
  156.                     new OdMatrix(od.getId(), origins, destinations, categorization, globalTimeVector, globalInterpolation);

  157.             // Add demand
  158.             MultiKeyMap<Set<Cell>> demandPerOD = new MultiKeyMap<>(Node.class, Node.class);
  159.             for (Cell cell : od.getCell())
  160.             {
  161.                 Node origin = otsNetwork.getNode(cell.getOrigin().get(eval));
  162.                 Node destination = otsNetwork.getNode(cell.getDestination().get(eval));
  163.                 demandPerOD.get(() -> new LinkedHashSet<>(), origin, destination).add(cell);
  164.             }
  165.             addDemand(categories, globalFactor, odMatrix, demandPerOD, eval);

  166.             // OD options
  167.             Set<GtuTemplate> templates = parseGtuTemplates(definitions, gtuTemplates, streamMap, eval);
  168.             OdOptions odOptions = parseOdOptions(otsNetwork, definitions, templates, definedLaneBiases, factories,
  169.                     modelIdReferrals, streamMap, odOptionsMap, od, categorization, eval);

  170.             // Invoke OdApplier
  171.             DetectorType detectorType = definitions.get(DetectorType.class, od.getSinkType().get(eval));
  172.             Map<String, GeneratorObjects> output =
  173.                     Try.assign(() -> OdApplier.applyOd(otsNetwork, odMatrix, odOptions, detectorType), XmlParserException.class,
  174.                             "Simulator time should be zero when parsing an OD.");

  175.             // Collect generators in output
  176.             for (GeneratorObjects generatorObject : output.values())
  177.             {
  178.                 generators.add(generatorObject.generator());
  179.             }
  180.         }

  181.         return generators;
  182.     }

  183.     /**
  184.      * Parse categories (save them in a map), and derive the categorization.
  185.      * @param otsNetwork RoadNetwork; network to obtain routes and lanes in categories.
  186.      * @param definitions Definitions; definitions to get GTU types in categories.
  187.      * @param od Od; OD with categories.
  188.      * @param categories Map&lt;String, Category&gt;; map to store categories in.
  189.      * @param eval Eval; expression evaluator.
  190.      * @return Categorization
  191.      * @throws XmlParserException when a category does not match the categorization.
  192.      */
  193.     private static Categorization parseCategories(final RoadNetwork otsNetwork, final Definitions definitions, final Od od,
  194.             final Map<String, Category> categories, final Eval eval) throws XmlParserException
  195.     {
  196.         Categorization categorization;
  197.         Map<String, Double> categoryFactors = new LinkedHashMap<>();
  198.         if (od.getCategory().isEmpty())
  199.         {
  200.             categorization = Categorization.UNCATEGORIZED;
  201.         }
  202.         else
  203.         {
  204.             List<Class<?>> categoryClasses = new ArrayList<>();
  205.             if (od.getCategory().get(0).getGtuType() != null)
  206.             {
  207.                 categoryClasses.add(GtuType.class);
  208.             }
  209.             if (od.getCategory().get(0).getRoute() != null)
  210.             {
  211.                 categoryClasses.add(Route.class);
  212.             }
  213.             if (od.getCategory().get(0).getLane() != null)
  214.             {
  215.                 categoryClasses.add(Lane.class);
  216.             }
  217.             if (categoryClasses.isEmpty())
  218.             {
  219.                 // XML uses categories, but these define nothing
  220.                 categorization = Categorization.UNCATEGORIZED;
  221.             }
  222.             else
  223.             {
  224.                 categorization = new Categorization("", categoryClasses.get(0),
  225.                         categoryClasses.subList(1, categoryClasses.size()).toArray(new Class<?>[categoryClasses.size() - 1]));
  226.             }
  227.             // create categories and check that all categories comply with the categorization
  228.             for (CategoryType category : od.getCategory())
  229.             {
  230.                 Throw.when(
  231.                         (categorization.entails(GtuType.class) && category.getGtuType() == null)
  232.                                 || (!categorization.entails(GtuType.class) && category.getGtuType() != null),
  233.                         XmlParserException.class, "Categories are inconsistent concerning GtuType.");
  234.                 Throw.when(
  235.                         (categorization.entails(Route.class) && category.getRoute() == null)
  236.                                 || (!categorization.entails(Route.class) && category.getRoute() != null),
  237.                         XmlParserException.class, "Categories are inconsistent concerning Route.");
  238.                 Throw.when(
  239.                         (categorization.entails(Lane.class) && category.getLane() == null)
  240.                                 || (!categorization.entails(Lane.class) && category.getLane() != null),
  241.                         XmlParserException.class, "Categories are inconsistent concerning Lane.");
  242.                 List<Object> objects = new ArrayList<>();
  243.                 if (categorization.entails(GtuType.class))
  244.                 {
  245.                     objects.add(definitions.get(GtuType.class, category.getGtuType().get(eval)));
  246.                 }
  247.                 if (categorization.entails(Route.class))
  248.                 {
  249.                     objects.add(otsNetwork.getRoute(category.getRoute().get(eval)));
  250.                 }
  251.                 if (categorization.entails(Lane.class))
  252.                 {
  253.                     CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(category.getLane().getLink().get(eval));
  254.                     Lane lane = (Lane) link.getCrossSectionElement(category.getLane().getLane().get(eval));
  255.                     objects.add(lane);
  256.                 }
  257.                 categories.put(category.getId(), new Category(categorization, objects.get(0),
  258.                         objects.subList(1, objects.size()).toArray(new Object[objects.size() - 1])));
  259.                 categoryFactors.put(category.getId(), category.getFactor().get(eval));
  260.             }
  261.         }
  262.         return categorization;
  263.     }

  264.     /**
  265.      * Add cell data to OD matrix.
  266.      * @param categories Map&lt;String, Category&gt;; map of parsed categories.
  267.      * @param globalFactor double; factor on entire OD.
  268.      * @param odMatrix OdMatrix; OD matrix to set demand data in.
  269.      * @param demandPerOD MultiKeyMap&lt;Set&lt;Cell&gt;&gt;; cell tags per origin and destination node.
  270.      * @param eval Eval; expression evaluator.
  271.      * @throws XmlParserException when data in inconsistently defined.
  272.      */
  273.     private static void addDemand(final Map<String, Category> categories, final double globalFactor, final OdMatrix odMatrix,
  274.             final MultiKeyMap<Set<Cell>> demandPerOD, final Eval eval) throws XmlParserException
  275.     {
  276.         Categorization categorization = odMatrix.getCategorization();
  277.         TimeVector globalTimeVector = odMatrix.getGlobalTimeVector();
  278.         Interpolation globalInterpolation = odMatrix.getGlobalInterpolation();
  279.         for (Object o : demandPerOD.getKeys())
  280.         {
  281.             MultiKeyMap<Set<Cell>> demandPerD = demandPerOD.getSubMap(o);
  282.             for (Object d : demandPerD.getKeys())
  283.             {
  284.                 Set<Cell> set = demandPerD.get(d);
  285.                 Node origin = (Node) o;
  286.                 Node destination = (Node) d;
  287.                 Throw.when(categorization.equals(Categorization.UNCATEGORIZED) && set.size() > 1, XmlParserException.class,
  288.                         "Multiple Cell tags define demand from %s to %s in uncategorized demand.", origin.getId(),
  289.                         destination.getId());

  290.                 // Find main cell, that may be split among other Cell tags between the same origin and destination
  291.                 Cell main = null;
  292.                 if (!categorization.equals(Categorization.UNCATEGORIZED))
  293.                 {
  294.                     for (Cell cell : set)
  295.                     {
  296.                         if (cell.getCategory() == null)
  297.                         {
  298.                             Throw.when(main != null, XmlParserException.class,
  299.                                     "Multiple Cell tags define main demand from %s to %s.", origin.getId(),
  300.                                     destination.getId());
  301.                             Throw.when(set.size() == 1, XmlParserException.class,
  302.                                     "Categorized demand from %s to %s has single Cell, and without category.", origin.getId(),
  303.                                     destination.getId());
  304.                             main = cell;
  305.                         }
  306.                     }
  307.                 }

  308.                 // Add cell per tag
  309.                 for (Cell cell : set)
  310.                 {
  311.                     // Skip main demand, it is split among other tags
  312.                     if (cell.equals(main))
  313.                     {
  314.                         continue;
  315.                     }

  316.                     // TimeVector: cell > main > global
  317.                     TimeVector timeVector = cell.getLevel() != null && cell.getLevel().get(0).getTime() != null
  318.                             ? parseTimeVector(cell.getLevel(), eval)
  319.                             : (main != null && main.getLevel() != null && main.getLevel().get(0).getTime() != null
  320.                                     ? parseTimeVector(main.getLevel(), eval) : globalTimeVector);

  321.                     // Interpolation: cell > main > global
  322.                     Interpolation interpolation = cell.getInterpolation() != null ? cell.getInterpolation().get(eval)
  323.                             : (main != null && main.getInterpolation() != null ? main.getInterpolation().get(eval)
  324.                                     : globalInterpolation);

  325.                     // Category
  326.                     Category category = categorization.equals(Categorization.UNCATEGORIZED) ? Category.UNCATEGORIZED
  327.                             : categories.get(cell.getCategory().get(eval));

  328.                     // Factor: (global * main * cell)
  329.                     double factor = globalFactor;
  330.                     factor = main == null ? factor : factor * main.getFactor().get(eval);
  331.                     factor = cell.getFactor() == null ? factor : factor * cell.getFactor().get(eval);

  332.                     // Figure out where the base demand, and optional factors are
  333.                     Frequency[] demandRaw = new Frequency[timeVector.size()];
  334.                     List<LevelTimeType> baseDemand;
  335.                     List<LevelTimeType> factors = null;
  336.                     if (cell.getLevel() == null)
  337.                     {
  338.                         // this demand specified no levels, use main demand
  339.                         baseDemand = main.getLevel();
  340.                     }
  341.                     else if (cell.getLevel().get(0).getValue().contains("veh"))
  342.                     {
  343.                         // this demand specifies levels
  344.                         baseDemand = cell.getLevel();
  345.                     }
  346.                     else
  347.                     {
  348.                         // this demand specifies factors on the main demand
  349.                         baseDemand = main.getLevel();
  350.                         factors = cell.getLevel();
  351.                     }
  352.                     // sort
  353.                     sortLevelTime(baseDemand, eval);
  354.                     if (factors != null)
  355.                     {
  356.                         sortLevelTime(factors, eval);
  357.                     }
  358.                     // fill array, include factors
  359.                     for (int i = 0; i < baseDemand.size(); i++)
  360.                     {
  361.                         Throw.when(
  362.                                 baseDemand.get(i).getTime() != null && factors != null && factors.get(i).getTime() != null
  363.                                         && !baseDemand.get(i).getTime().get(eval).eq(factors.get(i).getTime().get(eval)),
  364.                                 XmlParserException.class, "Demand from %s to %s is specified with factors that have "
  365.                                         + "different time from the base demand.",
  366.                                 origin, destination);
  367.                         demandRaw[i] = parseLevel(baseDemand.get(i).getValue(), factor * (factors == null ? 1.0
  368.                                 : new PositiveFactorAdapter().unmarshal(factors.get(i).getValue()).get(eval)));
  369.                     }
  370.                     FrequencyVector demandVector = new FrequencyVector(demandRaw, FrequencyUnit.SI);

  371.                     // Finally, add the demand
  372.                     odMatrix.putDemandVector(origin, destination, category, demandVector, timeVector, interpolation);
  373.                 }
  374.             }
  375.         }
  376.     }

  377.     /**
  378.      * Parse OD options.
  379.      * @param otsNetwork RoadNetwork; network to obtain routes and lanes in categories.
  380.      * @param definitions Definitions; definitions to get GTU types in categories.
  381.      * @param templates Set&lt;GtuTemplate&gt;; parsed GTU templates.
  382.      * @param definedLaneBiases Map&lt;String, LaneBias&gt;; parsed lane biases.
  383.      * @param factories Map&lt;String, LaneBasedStrategicalPlannerFactory&lt;?&gt;&gt;; parsed model factories.
  384.      * @param modelIdReferrals Map&lt;String, String&gt;; model id referrals.
  385.      * @param streamMap StreamInformation; parsed random streams.
  386.      * @param odOptionsMap Map&lt;String, org.opentrafficsim.xml.generated.OdOptions&gt;; gathered OdOptions tags.
  387.      * @param od Od; OD tag.
  388.      * @param categorization Categorization; categorization.
  389.      * @param eval Eval; expression evaluator.
  390.      * @return OdOptions.
  391.      * @throws XmlParserException when options in OD are not defined, or Markov chain not well defined.
  392.      */
  393.     private static OdOptions parseOdOptions(final RoadNetwork otsNetwork, final Definitions definitions,
  394.             final Set<GtuTemplate> templates, final Map<String, LaneBias> definedLaneBiases,
  395.             final Map<String, LaneBasedStrategicalPlannerFactory<?>> factories, final Map<String, String> modelIdReferrals,
  396.             final StreamInformation streamMap, final Map<String, org.opentrafficsim.xml.generated.OdOptions> odOptionsMap,
  397.             final Od od, final Categorization categorization, final Eval eval) throws XmlParserException
  398.     {
  399.         OdOptions odOptions =
  400.                 new OdOptions().set(OdOptions.GTU_ID, new IdGenerator("")).set(OdOptions.NO_LC_DIST, Length.instantiateSI(1.0));

  401.         // default global option to integrate defined templates
  402.         StreamInterface stream = streamMap.getStream("generation");
  403.         LaneBasedStrategicalRoutePlannerFactory defaultLmrsFactory =
  404.                 DefaultLaneBasedGtuCharacteristicsGeneratorOd.defaultLmrs(stream);
  405.         Factory characteristicsGeneratorFactory = new Factory(defaultLmrsFactory);
  406.         characteristicsGeneratorFactory.setTemplates(templates);
  407.         odOptions.set(OdOptions.GTU_TYPE, characteristicsGeneratorFactory.create());
  408.         // other options
  409.         if (od.getOptions() != null)
  410.         {
  411.             Throw.when(!odOptionsMap.containsKey(od.getOptions().get(eval)), XmlParserException.class,
  412.                     "OD options in OD %s not defined.", od.getId());
  413.             for (OdOptionsItem option : odOptionsMap.get(od.getOptions().get(eval)).getOdOptionsItem())
  414.             {
  415.                 /*
  416.                  * The current 'options' is valid within a single context, i.e. global, link type, origin or lane. All option
  417.                  * values are set in odOptions for that context, in the current loop. For the model factories an implementation
  418.                  * of DefaultLaneBasedGtuCharacteristicsGeneratorOd is created that responds to the GTU type, and selects a
  419.                  * factory assigned to that GTU type within the context. Or, the default factory in the context is used. Or
  420.                  * finally, a default LMRS. If no model factory is specified in the context (nor a higher context), no option
  421.                  * value is set and OdOptions itself returns a default LMRS factory.
  422.                  */

  423.                 // GTU type (model)
  424.                 parseModelOption(otsNetwork, definitions, factories, modelIdReferrals, odOptions, templates, defaultLmrsFactory,
  425.                         option, eval);

  426.                 // no lc
  427.                 if (option.getNoLaneChange() != null)
  428.                 {
  429.                     setOption(odOptions, OdOptions.NO_LC_DIST, option.getNoLaneChange().get(eval), option, otsNetwork,
  430.                             definitions, eval);
  431.                 }

  432.                 // room checker
  433.                 setOption(odOptions, OdOptions.ROOM_CHECKER, ParseUtil.parseRoomChecker(option.getRoomChecker(), eval), option,
  434.                         otsNetwork, definitions, eval);

  435.                 // headway distribution
  436.                 if (option.getHeadwayDist() != null)
  437.                 {
  438.                     setOption(odOptions, OdOptions.HEADWAY_DIST, option.getHeadwayDist().get(eval), option, otsNetwork,
  439.                             definitions, eval);
  440.                 }

  441.                 // markov
  442.                 if (option.getMarkov() != null)
  443.                 {
  444.                     Throw.when(!categorization.entails(GtuType.class), XmlParserException.class,
  445.                             "The OD option Markov can only be used if GtuType is in the CATEGORY's.");
  446.                     Throw.when(!categorization.entails(Lane.class) && option.getLane() != null, XmlParserException.class,
  447.                             "Markov chains at lane level are not used if Lane's are not in the CATEGORY's.");
  448.                     MarkovCorrelation<GtuType, Frequency> markov = new MarkovCorrelation<>();
  449.                     for (State state : option.getMarkov().getState())
  450.                     {
  451.                         GtuType gtuType = definitions.get(GtuType.class, state.getGtuType().get(eval));
  452.                         double correlation = state.getCorrelation().get(eval);
  453.                         if (state.getParent() == null)
  454.                         {
  455.                             markov.addState(gtuType, correlation);
  456.                         }
  457.                         else
  458.                         {
  459.                             GtuType parentType = definitions.get(GtuType.class, state.getParent().get(eval));
  460.                             markov.addState(parentType, gtuType, correlation);
  461.                         }
  462.                     }
  463.                     setOption(odOptions, OdOptions.MARKOV, markov, option, otsNetwork, definitions, eval);
  464.                 }

  465.                 // lane biases
  466.                 if (option.getLaneBiases() != null)
  467.                 {
  468.                     LaneBiases laneBiases = new LaneBiases();
  469.                     for (org.opentrafficsim.xml.generated.LaneBias laneBiasType : option.getLaneBiases().getLaneBias())
  470.                     {
  471.                         String gtuTypeId = laneBiasType.getGtuType().get(eval);
  472.                         GtuType gtuType = definitions.get(GtuType.class, gtuTypeId);
  473.                         Throw.whenNull(gtuType, "GTU type %s in lane bias does not exist.", gtuTypeId);
  474.                         laneBiases.addBias(gtuType, DefinitionsParser.parseLaneBias(laneBiasType, eval));
  475.                     }
  476.                     for (DefinedLaneBias definedLaneBias : option.getLaneBiases().getDefinedLaneBias())
  477.                     {
  478.                         String gtuTypeId = definedLaneBias.getGtuType().get(eval);
  479.                         GtuType gtuType = definitions.get(GtuType.class, gtuTypeId);
  480.                         Throw.whenNull(gtuType, "GTU type %s in defined lane bias does not exist.", gtuTypeId);
  481.                         laneBiases.addBias(gtuType, definedLaneBiases.get(definedLaneBias.getGtuType().get(eval)));
  482.                     }
  483.                     setOption(odOptions, OdOptions.LANE_BIAS, laneBiases, option, otsNetwork, definitions, eval);
  484.                 }

  485.             }
  486.         }
  487.         return odOptions;
  488.     }

  489.     /**
  490.      * Parse OD model option.
  491.      * @param otsNetwork RoadNetwork; network to obtain routes and lanes in categories.
  492.      * @param definitions Definitions; definitions to get GTU types in categories.
  493.      * @param factories Map&lt;String, LaneBasedStrategicalPlannerFactory&lt;?&gt;&gt;; parsed model factories.
  494.      * @param modelIdReferrals Map&lt;String, String&gt;; model id referrals.
  495.      * @param odOptions OdOptions; OD options.
  496.      * @param templates Set&lt;GtuTemplate&gt;; parsed GTU templates.
  497.      * @param defaultLmrsFactory LaneBasedStrategicalRoutePlannerFactory; default LMRS factory.
  498.      * @param option OdOptionItem; OD option item tag.
  499.      * @param eval Eval; expression evaluator.
  500.      * @throws XmlParserException when a non-existent model is referred.
  501.      */
  502.     private static void parseModelOption(final RoadNetwork otsNetwork, final Definitions definitions,
  503.             final Map<String, LaneBasedStrategicalPlannerFactory<?>> factories, final Map<String, String> modelIdReferrals,
  504.             final OdOptions odOptions, final Set<GtuTemplate> templates,
  505.             final LaneBasedStrategicalRoutePlannerFactory defaultLmrsFactory, final OdOptionsItem option, final Eval eval)
  506.             throws XmlParserException
  507.     {
  508.         Factory characteristicsGeneratorFactory;
  509.         if (option.getDefaultModel() != null || (option.getModel() != null && !option.getModel().isEmpty()))
  510.         {
  511.             LaneBasedStrategicalPlannerFactory<?> defaultFactory;
  512.             if (option.getDefaultModel() != null)
  513.             {
  514.                 // TODO: model id referral
  515.                 String modelId = OdParser.getModelId(option.getDefaultModel(), modelIdReferrals, eval);
  516.                 Throw.when(!factories.containsKey(modelId), XmlParserException.class,
  517.                         "OD option DefaultModel refers to a non-existent model with ID %s.", modelId);
  518.                 defaultFactory = factories.get(modelId);
  519.             }
  520.             else
  521.             {
  522.                 defaultFactory = null;
  523.             }
  524.             // compose map that couples GTU types to factories through Model ID's
  525.             final Map<GtuType, LaneBasedStrategicalPlannerFactory<?>> gtuTypeFactoryMap = new LinkedHashMap<>();
  526.             if (option.getModel() != null)
  527.             {
  528.                 for (Model model : option.getModel())
  529.                 {
  530.                     GtuType gtuType = definitions.get(GtuType.class, model.getGtuType().get(eval));
  531.                     Throw.when(!factories.containsKey(model.getId().get(eval)), XmlParserException.class,
  532.                             "OD option Model refers to a non existent-model with ID %s.", model.getId());
  533.                     gtuTypeFactoryMap.put(gtuType, factories.get(getModelId(model, modelIdReferrals, eval)));
  534.                 }
  535.             }

  536.             LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> factoryByGtuType =
  537.                     new LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner>()
  538.                     {
  539.                         /** {@inheritDoc} */
  540.                         @Override
  541.                         public LaneBasedStrategicalPlanner create(final LaneBasedGtu gtu, final Route route, final Node origin,
  542.                                 final Node destination) throws GtuException
  543.                         {
  544.                             LaneBasedStrategicalPlannerFactory<?> strategicalPlannerFactory =
  545.                                     gtuTypeFactoryMap.get(gtu.getType());
  546.                             if (strategicalPlannerFactory != null)
  547.                             {
  548.                                 // a model factory for this GTU type is specified
  549.                                 return strategicalPlannerFactory.create(gtu, route, origin, destination);
  550.                             }
  551.                             if (defaultFactory != null)
  552.                             {
  553.                                 // a default model factory is specified
  554.                                 return defaultFactory.create(gtu, route, origin, destination);
  555.                             }
  556.                             return defaultLmrsFactory.create(gtu, route, origin, destination);
  557.                         }
  558.                     };
  559.             characteristicsGeneratorFactory = new Factory(factoryByGtuType).setTemplates(templates);
  560.             setOption(odOptions, OdOptions.GTU_TYPE, characteristicsGeneratorFactory.create(), option, otsNetwork, definitions,
  561.                     eval);
  562.         }
  563.     }

  564.     /**
  565.      * @param definitions Definitions; definitions to get GTU types in categories.
  566.      * @param gtuTemplates Map&lt;String, org.opentrafficsim.xml.generated.GtuTemplate&gt;; GTU template tags.
  567.      * @param streamMap StreamInformation; random streams.
  568.      * @param eval Eval; expression evaluator.
  569.      * @return GTU templates.
  570.      * @throws XmlParserException when a distribution cannot be parsed.
  571.      */
  572.     private static Set<GtuTemplate> parseGtuTemplates(final Definitions definitions,
  573.             final Map<String, org.opentrafficsim.xml.generated.GtuTemplate> gtuTemplates, final StreamInformation streamMap,
  574.             final Eval eval) throws XmlParserException
  575.     {
  576.         Set<GtuTemplate> templates = new LinkedHashSet<>();
  577.         for (org.opentrafficsim.xml.generated.GtuTemplate template : gtuTemplates.values())
  578.         {
  579.             GtuType gtuType = definitions.get(GtuType.class, template.getGtuType().get(eval));
  580.             Generator<Length> lengthGenerator = ParseDistribution.parseContinuousDist(streamMap, template.getLengthDist(),
  581.                     template.getLengthDist().getLengthUnit().get(eval), eval);
  582.             Generator<Length> widthGenerator = ParseDistribution.parseContinuousDist(streamMap, template.getWidthDist(),
  583.                     template.getWidthDist().getLengthUnit().get(eval), eval);
  584.             Generator<Speed> maximumSpeedGenerator = ParseDistribution.parseContinuousDist(streamMap,
  585.                     template.getMaxSpeedDist(), template.getMaxSpeedDist().getSpeedUnit().get(eval), eval);
  586.             if (template.getMaxAccelerationDist() == null || template.getMaxDecelerationDist() == null)
  587.             {
  588.                 templates.add(new GtuTemplate(gtuType, lengthGenerator, widthGenerator, maximumSpeedGenerator));
  589.             }
  590.             else
  591.             {
  592.                 Generator<Acceleration> maxAccelerationGenerator =
  593.                         ParseDistribution.parseContinuousDist(streamMap, template.getMaxAccelerationDist(),
  594.                                 template.getMaxAccelerationDist().getAccelerationUnit().get(eval), eval);
  595.                 Generator<Acceleration> maxDecelerationGenerator =
  596.                         ParseDistribution.parseContinuousDist(streamMap, template.getMaxDecelerationDist(),
  597.                                 template.getMaxDecelerationDist().getAccelerationUnit().get(eval), eval);
  598.                 templates.add(new GtuTemplate(gtuType, lengthGenerator, widthGenerator, maximumSpeedGenerator,
  599.                         maxAccelerationGenerator, maxDecelerationGenerator));
  600.             }
  601.         }
  602.         return templates;
  603.     }

  604.     /**
  605.      * Parse the value of a LevelTimeType that specifies flow (i.e. with 'veh' per time unit).
  606.      * @param string String; value of LevelTimeType
  607.      * @param factor double; total applicable factor on this level
  608.      * @return Frequency; resulting frequency
  609.      */
  610.     private static Frequency parseLevel(final String string, final double factor)
  611.     {
  612.         return Frequency.valueOf(string.replace("veh", "")).times(factor);
  613.     }

  614.     /**
  615.      * Sorts LevelTimeType in a list by the time value, if any.
  616.      * @param levelTime List&lt;LevelTimeType&gt;; sorted list
  617.      * @param eval Eval; expression evaluator.
  618.      */
  619.     private static void sortLevelTime(final List<LevelTimeType> levelTime, final Eval eval)
  620.     {
  621.         Collections.sort(levelTime, new Comparator<LevelTimeType>()
  622.         {
  623.             /** {@inheritDoc} */
  624.             @Override
  625.             public int compare(final LevelTimeType o1, final LevelTimeType o2)
  626.             {
  627.                 if (o1.getTime() == null && o2.getTime() == null)
  628.                 {
  629.                     return 0;
  630.                 }
  631.                 if (o1.getTime() == null)
  632.                 {
  633.                     return -1;
  634.                 }
  635.                 if (o2.getTime() == null)
  636.                 {
  637.                     return 1;
  638.                 }
  639.                 return o1.getTime().get(eval).compareTo(o2.getTime().get(eval));
  640.             }
  641.         });
  642.     }

  643.     /**
  644.      * Parse a list of {@code LevelTimeType} to a {@code TimeVector}.
  645.      * @param list List&lt;LevelTimeType&gt;; list of time information
  646.      * @param eval Eval; expression evaluator.
  647.      * @return TimeVector; time vector
  648.      * @throws XmlParserException if global time has no values
  649.      */
  650.     private static TimeVector parseTimeVector(final List<LevelTimeType> list, final Eval eval) throws XmlParserException
  651.     {
  652.         List<Time> timeList = new ArrayList<>();
  653.         for (LevelTimeType time : list)
  654.         {
  655.             timeList.add(time.getTime().get(eval));
  656.         }
  657.         Collections.sort(timeList);
  658.         return new TimeVector(timeList, TimeUnit.DEFAULT);
  659.     }

  660.     /**
  661.      * Set option.
  662.      * @param odOptions OdOptions; OD options to set the option in
  663.      * @param option Option&lt;T&gt;; option to set
  664.      * @param value T; value to set the option to
  665.      * @param options OdOptionsItem; used to set the option on the right level (Link type, origin node, lane
  666.      * @param otsNetwork RoadNetwork; to get the link type, origin node or lane from
  667.      * @param definitions Definitions; parsed definitions.
  668.      * @param eval Eval; expression evaluator.
  669.      * @param <T> option value type
  670.      */
  671.     private static <T> void setOption(final OdOptions odOptions, final Option<T> option, final T value,
  672.             final OdOptionsItem options, final RoadNetwork otsNetwork, final Definitions definitions, final Eval eval)
  673.     {
  674.         if (value != null)
  675.         {
  676.             if (options.getLinkType() != null)
  677.             {
  678.                 odOptions.set(definitions.get(LinkType.class, options.getLinkType().get(eval)), option, value);
  679.             }
  680.             else if (options.getOrigin() != null)
  681.             {
  682.                 odOptions.set(otsNetwork.getNode(options.getOrigin().get(eval)), option, value);
  683.             }
  684.             else if (options.getLane() != null)
  685.             {
  686.                 CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(options.getLane().getLink().get(eval));
  687.                 odOptions.set((Lane) link.getCrossSectionElement(options.getLane().getLane().get(eval)), option, value);
  688.             }
  689.             else
  690.             {
  691.                 odOptions.set(option, value);
  692.             }
  693.         }
  694.     }

  695.     /**
  696.      * Returns the ID of a default model, referred if there is a referral specified.
  697.      * @param model String; model
  698.      * @param modelIdReferrals String; model ID
  699.      * @param eval Eval; expression evaluator.
  700.      * @return ID of a model, referred if there is a referral specified
  701.      */
  702.     private static String getModelId(final DefaultModel model, final Map<String, String> modelIdReferrals, final Eval eval)
  703.     {
  704.         if (model.getModelIdReferral() != null)
  705.         {
  706.             return modelIdReferrals.get(model.getModelIdReferral().get(eval));
  707.         }
  708.         return model.getId().get(eval);
  709.     }

  710.     /**
  711.      * Returns the ID of a model, referred if there is a referral specified.
  712.      * @param model String; model
  713.      * @param modelIdReferrals String; model ID
  714.      * @param eval Eval; expression evaluator.
  715.      * @return ID of a model, referred if there is a referral specified
  716.      */
  717.     private static String getModelId(final Model model, final Map<String, String> modelIdReferrals, final Eval eval)
  718.     {
  719.         if (model.getModelIdReferral() != null)
  720.         {
  721.             return modelIdReferrals.get(model.getModelIdReferral().get(eval));
  722.         }
  723.         return model.getId().get(eval);
  724.     }

  725. }