OdParser.java
package org.opentrafficsim.road.network.factory.xml.parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.djunits.unit.FrequencyUnit;
import org.djunits.unit.TimeUnit;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Frequency;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.djunits.value.vdouble.scalar.Time;
import org.djunits.value.vdouble.vector.FrequencyVector;
import org.djunits.value.vdouble.vector.TimeVector;
import org.djutils.eval.Eval;
import org.djutils.exceptions.Throw;
import org.djutils.exceptions.Try;
import org.djutils.multikeymap.MultiKeyMap;
import org.opentrafficsim.core.definitions.Definitions;
import org.opentrafficsim.core.distributions.Generator;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.core.gtu.GtuTemplate;
import org.opentrafficsim.core.gtu.GtuType;
import org.opentrafficsim.core.idgenerator.IdGenerator;
import org.opentrafficsim.core.network.LinkType;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.core.network.route.Route;
import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBias;
import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator;
import org.opentrafficsim.road.gtu.generator.MarkovCorrelation;
import org.opentrafficsim.road.gtu.generator.characteristics.DefaultLaneBasedGtuCharacteristicsGeneratorOd;
import org.opentrafficsim.road.gtu.generator.characteristics.DefaultLaneBasedGtuCharacteristicsGeneratorOd.Factory;
import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalRoutePlannerFactory;
import org.opentrafficsim.road.network.RoadNetwork;
import org.opentrafficsim.road.network.factory.xml.XmlParserException;
import org.opentrafficsim.road.network.factory.xml.utils.ParseDistribution;
import org.opentrafficsim.road.network.factory.xml.utils.ParseUtil;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.object.detector.DetectorType;
import org.opentrafficsim.road.od.Categorization;
import org.opentrafficsim.road.od.Category;
import org.opentrafficsim.road.od.Interpolation;
import org.opentrafficsim.road.od.OdApplier;
import org.opentrafficsim.road.od.OdApplier.GeneratorObjects;
import org.opentrafficsim.road.od.OdMatrix;
import org.opentrafficsim.road.od.OdOptions;
import org.opentrafficsim.road.od.OdOptions.Option;
import org.opentrafficsim.xml.bindings.PositiveFactorAdapter;
import org.opentrafficsim.xml.generated.CategoryType;
import org.opentrafficsim.xml.generated.Demand;
import org.opentrafficsim.xml.generated.LevelTimeType;
import org.opentrafficsim.xml.generated.Od;
import org.opentrafficsim.xml.generated.Od.Cell;
import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem;
import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem.DefaultModel;
import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem.LaneBiases.DefinedLaneBias;
import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem.Markov.State;
import org.opentrafficsim.xml.generated.OdOptions.OdOptionsItem.Model;
import nl.tudelft.simulation.dsol.experiment.StreamInformation;
import nl.tudelft.simulation.jstats.streams.StreamInterface;
/**
* This utility creates GTU generators from an OD matrix.
* <p>
* Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
* @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
*/
public final class OdParser
{
/** */
private OdParser()
{
// static class
}
/**
* Creates generators and returns OD matrices.
* @param otsNetwork RoadNetwork; network
* @param definitions Definitions; parsed definitions.
* @param demand Demand; demand
* @param gtuTemplates Map<String, org.opentrafficsim.xml.generated.GtuTemplate>; GTU templates
* @param definedLaneBiases Map<String, LaneBias<?>>; defined lane biases
* @param factories Map<String, LaneBasedStrategicalPlannerFactory<?>>; factories from model parser
* @param modelIdReferrals Map<String, String>; model id referrals
* @param streamMap Map<String, StreamInformation>; stream map
* @param eval Eval; expression evaluator.
* @return List<LaneBasedGtuGenerator>; generators
* @throws XmlParserException if the OD contains an inconsistency or error
*/
public static List<LaneBasedGtuGenerator> parseDemand(final RoadNetwork otsNetwork, final Definitions definitions,
final Demand demand, final Map<String, org.opentrafficsim.xml.generated.GtuTemplate> gtuTemplates,
final Map<String, LaneBias> definedLaneBiases, final Map<String, LaneBasedStrategicalPlannerFactory<?>> factories,
final Map<String, String> modelIdReferrals, final StreamInformation streamMap, final Eval eval)
throws XmlParserException
{
List<LaneBasedGtuGenerator> generators = new ArrayList<>();
// Collect options
Map<String, org.opentrafficsim.xml.generated.OdOptions> odOptionsMap = new LinkedHashMap<>();
for (org.opentrafficsim.xml.generated.OdOptions odOptions : demand.getOdOptions())
{
odOptionsMap.put(odOptions.getId(), odOptions);
}
for (Od od : demand.getOd())
{
// Origins and destinations, retrieve them from demand items
List<Node> origins = new ArrayList<>();
List<Node> destinations = new ArrayList<>();
for (Cell cell : od.getCell())
{
String originId = cell.getOrigin().get(eval);
if (!origins.contains(otsNetwork.getNode(originId)))
{
Node originNode = otsNetwork.getNode(originId);
Throw.whenNull(originNode, "Parse demand: cannot find origin %s", originId);
origins.add(originNode);
}
String destinationId = cell.getDestination().get(eval);
if (!destinations.contains(otsNetwork.getNode(destinationId)))
{
Node destinationNode = otsNetwork.getNode(destinationId);
Throw.whenNull(destinationNode, "Parse demand: cannot find destination %s", destinationId);
destinations.add(destinationNode);
}
}
// Create categorization
Map<String, Category> categories = new LinkedHashMap<>();
Categorization categorization = parseCategories(otsNetwork, definitions, od, categories, eval);
// Global time vector
TimeVector globalTimeVector = null;
if (od.getGlobalTime() != null)
{
List<Time> timeList = new ArrayList<>();
for (org.opentrafficsim.xml.generated.GlobalTimeType.Time time : od.getGlobalTime().getTime())
{
timeList.add(time.getValue().get(eval));
}
Collections.sort(timeList);
globalTimeVector = Try.assign(() -> new TimeVector(timeList, TimeUnit.DEFAULT), XmlParserException.class,
"Global time has no values.");
}
Interpolation globalInterpolation = od.getGlobalInterpolation().get(eval);
double globalFactor = od.getGlobalFactor().get(eval);
// Create the OD matrix
OdMatrix odMatrix =
new OdMatrix(od.getId(), origins, destinations, categorization, globalTimeVector, globalInterpolation);
// Add demand
MultiKeyMap<Set<Cell>> demandPerOD = new MultiKeyMap<>(Node.class, Node.class);
for (Cell cell : od.getCell())
{
Node origin = otsNetwork.getNode(cell.getOrigin().get(eval));
Node destination = otsNetwork.getNode(cell.getDestination().get(eval));
demandPerOD.get(() -> new LinkedHashSet<>(), origin, destination).add(cell);
}
addDemand(categories, globalFactor, odMatrix, demandPerOD, eval);
// OD options
Set<GtuTemplate> templates = parseGtuTemplates(definitions, gtuTemplates, streamMap, eval);
OdOptions odOptions = parseOdOptions(otsNetwork, definitions, templates, definedLaneBiases, factories,
modelIdReferrals, streamMap, odOptionsMap, od, categorization, eval);
// Invoke OdApplier
DetectorType detectorType = definitions.get(DetectorType.class, od.getSinkType().get(eval));
Map<String, GeneratorObjects> output =
Try.assign(() -> OdApplier.applyOd(otsNetwork, odMatrix, odOptions, detectorType), XmlParserException.class,
"Simulator time should be zero when parsing an OD.");
// Collect generators in output
for (GeneratorObjects generatorObject : output.values())
{
generators.add(generatorObject.generator());
}
}
return generators;
}
/**
* Parse categories (save them in a map), and derive the categorization.
* @param otsNetwork RoadNetwork; network to obtain routes and lanes in categories.
* @param definitions Definitions; definitions to get GTU types in categories.
* @param od Od; OD with categories.
* @param categories Map<String, Category>; map to store categories in.
* @param eval Eval; expression evaluator.
* @return Categorization
* @throws XmlParserException when a category does not match the categorization.
*/
private static Categorization parseCategories(final RoadNetwork otsNetwork, final Definitions definitions, final Od od,
final Map<String, Category> categories, final Eval eval) throws XmlParserException
{
Categorization categorization;
Map<String, Double> categoryFactors = new LinkedHashMap<>();
if (od.getCategory().isEmpty())
{
categorization = Categorization.UNCATEGORIZED;
}
else
{
List<Class<?>> categoryClasses = new ArrayList<>();
if (od.getCategory().get(0).getGtuType() != null)
{
categoryClasses.add(GtuType.class);
}
if (od.getCategory().get(0).getRoute() != null)
{
categoryClasses.add(Route.class);
}
if (od.getCategory().get(0).getLane() != null)
{
categoryClasses.add(Lane.class);
}
if (categoryClasses.isEmpty())
{
// XML uses categories, but these define nothing
categorization = Categorization.UNCATEGORIZED;
}
else
{
categorization = new Categorization("", categoryClasses.get(0),
categoryClasses.subList(1, categoryClasses.size()).toArray(new Class<?>[categoryClasses.size() - 1]));
}
// create categories and check that all categories comply with the categorization
for (CategoryType category : od.getCategory())
{
Throw.when(
(categorization.entails(GtuType.class) && category.getGtuType() == null)
|| (!categorization.entails(GtuType.class) && category.getGtuType() != null),
XmlParserException.class, "Categories are inconsistent concerning GtuType.");
Throw.when(
(categorization.entails(Route.class) && category.getRoute() == null)
|| (!categorization.entails(Route.class) && category.getRoute() != null),
XmlParserException.class, "Categories are inconsistent concerning Route.");
Throw.when(
(categorization.entails(Lane.class) && category.getLane() == null)
|| (!categorization.entails(Lane.class) && category.getLane() != null),
XmlParserException.class, "Categories are inconsistent concerning Lane.");
List<Object> objects = new ArrayList<>();
if (categorization.entails(GtuType.class))
{
objects.add(definitions.get(GtuType.class, category.getGtuType().get(eval)));
}
if (categorization.entails(Route.class))
{
objects.add(otsNetwork.getRoute(category.getRoute().get(eval)));
}
if (categorization.entails(Lane.class))
{
CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(category.getLane().getLink().get(eval));
Lane lane = (Lane) link.getCrossSectionElement(category.getLane().getLane().get(eval));
objects.add(lane);
}
categories.put(category.getId(), new Category(categorization, objects.get(0),
objects.subList(1, objects.size()).toArray(new Object[objects.size() - 1])));
categoryFactors.put(category.getId(), category.getFactor().get(eval));
}
}
return categorization;
}
/**
* Add cell data to OD matrix.
* @param categories Map<String, Category>; map of parsed categories.
* @param globalFactor double; factor on entire OD.
* @param odMatrix OdMatrix; OD matrix to set demand data in.
* @param demandPerOD MultiKeyMap<Set<Cell>>; cell tags per origin and destination node.
* @param eval Eval; expression evaluator.
* @throws XmlParserException when data in inconsistently defined.
*/
private static void addDemand(final Map<String, Category> categories, final double globalFactor, final OdMatrix odMatrix,
final MultiKeyMap<Set<Cell>> demandPerOD, final Eval eval) throws XmlParserException
{
Categorization categorization = odMatrix.getCategorization();
TimeVector globalTimeVector = odMatrix.getGlobalTimeVector();
Interpolation globalInterpolation = odMatrix.getGlobalInterpolation();
for (Object o : demandPerOD.getKeys())
{
MultiKeyMap<Set<Cell>> demandPerD = demandPerOD.getSubMap(o);
for (Object d : demandPerD.getKeys())
{
Set<Cell> set = demandPerD.get(d);
Node origin = (Node) o;
Node destination = (Node) d;
Throw.when(categorization.equals(Categorization.UNCATEGORIZED) && set.size() > 1, XmlParserException.class,
"Multiple Cell tags define demand from %s to %s in uncategorized demand.", origin.getId(),
destination.getId());
// Find main cell, that may be split among other Cell tags between the same origin and destination
Cell main = null;
if (!categorization.equals(Categorization.UNCATEGORIZED))
{
for (Cell cell : set)
{
if (cell.getCategory() == null)
{
Throw.when(main != null, XmlParserException.class,
"Multiple Cell tags define main demand from %s to %s.", origin.getId(),
destination.getId());
Throw.when(set.size() == 1, XmlParserException.class,
"Categorized demand from %s to %s has single Cell, and without category.", origin.getId(),
destination.getId());
main = cell;
}
}
}
// Add cell per tag
for (Cell cell : set)
{
// Skip main demand, it is split among other tags
if (cell.equals(main))
{
continue;
}
// TimeVector: cell > main > global
TimeVector timeVector = cell.getLevel() != null && cell.getLevel().get(0).getTime() != null
? parseTimeVector(cell.getLevel(), eval)
: (main != null && main.getLevel() != null && main.getLevel().get(0).getTime() != null
? parseTimeVector(main.getLevel(), eval) : globalTimeVector);
// Interpolation: cell > main > global
Interpolation interpolation = cell.getInterpolation() != null ? cell.getInterpolation().get(eval)
: (main != null && main.getInterpolation() != null ? main.getInterpolation().get(eval)
: globalInterpolation);
// Category
Category category = categorization.equals(Categorization.UNCATEGORIZED) ? Category.UNCATEGORIZED
: categories.get(cell.getCategory().get(eval));
// Factor: (global * main * cell)
double factor = globalFactor;
factor = main == null ? factor : factor * main.getFactor().get(eval);
factor = cell.getFactor() == null ? factor : factor * cell.getFactor().get(eval);
// Figure out where the base demand, and optional factors are
Frequency[] demandRaw = new Frequency[timeVector.size()];
List<LevelTimeType> baseDemand;
List<LevelTimeType> factors = null;
if (cell.getLevel() == null)
{
// this demand specified no levels, use main demand
baseDemand = main.getLevel();
}
else if (cell.getLevel().get(0).getValue().contains("veh"))
{
// this demand specifies levels
baseDemand = cell.getLevel();
}
else
{
// this demand specifies factors on the main demand
baseDemand = main.getLevel();
factors = cell.getLevel();
}
// sort
sortLevelTime(baseDemand, eval);
if (factors != null)
{
sortLevelTime(factors, eval);
}
// fill array, include factors
for (int i = 0; i < baseDemand.size(); i++)
{
Throw.when(
baseDemand.get(i).getTime() != null && factors != null && factors.get(i).getTime() != null
&& !baseDemand.get(i).getTime().get(eval).eq(factors.get(i).getTime().get(eval)),
XmlParserException.class, "Demand from %s to %s is specified with factors that have "
+ "different time from the base demand.",
origin, destination);
demandRaw[i] = parseLevel(baseDemand.get(i).getValue(), factor * (factors == null ? 1.0
: new PositiveFactorAdapter().unmarshal(factors.get(i).getValue()).get(eval)));
}
FrequencyVector demandVector = new FrequencyVector(demandRaw, FrequencyUnit.SI);
// Finally, add the demand
odMatrix.putDemandVector(origin, destination, category, demandVector, timeVector, interpolation);
}
}
}
}
/**
* Parse OD options.
* @param otsNetwork RoadNetwork; network to obtain routes and lanes in categories.
* @param definitions Definitions; definitions to get GTU types in categories.
* @param templates Set<GtuTemplate>; parsed GTU templates.
* @param definedLaneBiases Map<String, LaneBias>; parsed lane biases.
* @param factories Map<String, LaneBasedStrategicalPlannerFactory<?>>; parsed model factories.
* @param modelIdReferrals Map<String, String>; model id referrals.
* @param streamMap StreamInformation; parsed random streams.
* @param odOptionsMap Map<String, org.opentrafficsim.xml.generated.OdOptions>; gathered OdOptions tags.
* @param od Od; OD tag.
* @param categorization Categorization; categorization.
* @param eval Eval; expression evaluator.
* @return OdOptions.
* @throws XmlParserException when options in OD are not defined, or Markov chain not well defined.
*/
private static OdOptions parseOdOptions(final RoadNetwork otsNetwork, final Definitions definitions,
final Set<GtuTemplate> templates, final Map<String, LaneBias> definedLaneBiases,
final Map<String, LaneBasedStrategicalPlannerFactory<?>> factories, final Map<String, String> modelIdReferrals,
final StreamInformation streamMap, final Map<String, org.opentrafficsim.xml.generated.OdOptions> odOptionsMap,
final Od od, final Categorization categorization, final Eval eval) throws XmlParserException
{
OdOptions odOptions =
new OdOptions().set(OdOptions.GTU_ID, new IdGenerator("")).set(OdOptions.NO_LC_DIST, Length.instantiateSI(1.0));
// default global option to integrate defined templates
StreamInterface stream = streamMap.getStream("generation");
LaneBasedStrategicalRoutePlannerFactory defaultLmrsFactory =
DefaultLaneBasedGtuCharacteristicsGeneratorOd.defaultLmrs(stream);
Factory characteristicsGeneratorFactory = new Factory(defaultLmrsFactory);
characteristicsGeneratorFactory.setTemplates(templates);
odOptions.set(OdOptions.GTU_TYPE, characteristicsGeneratorFactory.create());
// other options
if (od.getOptions() != null)
{
Throw.when(!odOptionsMap.containsKey(od.getOptions().get(eval)), XmlParserException.class,
"OD options in OD %s not defined.", od.getId());
for (OdOptionsItem option : odOptionsMap.get(od.getOptions().get(eval)).getOdOptionsItem())
{
/*
* The current 'options' is valid within a single context, i.e. global, link type, origin or lane. All option
* values are set in odOptions for that context, in the current loop. For the model factories an implementation
* of DefaultLaneBasedGtuCharacteristicsGeneratorOd is created that responds to the GTU type, and selects a
* factory assigned to that GTU type within the context. Or, the default factory in the context is used. Or
* finally, a default LMRS. If no model factory is specified in the context (nor a higher context), no option
* value is set and OdOptions itself returns a default LMRS factory.
*/
// GTU type (model)
parseModelOption(otsNetwork, definitions, factories, modelIdReferrals, odOptions, templates, defaultLmrsFactory,
option, eval);
// no lc
if (option.getNoLaneChange() != null)
{
setOption(odOptions, OdOptions.NO_LC_DIST, option.getNoLaneChange().get(eval), option, otsNetwork,
definitions, eval);
}
// room checker
setOption(odOptions, OdOptions.ROOM_CHECKER, ParseUtil.parseRoomChecker(option.getRoomChecker(), eval), option,
otsNetwork, definitions, eval);
// headway distribution
if (option.getHeadwayDist() != null)
{
setOption(odOptions, OdOptions.HEADWAY_DIST, option.getHeadwayDist().get(eval), option, otsNetwork,
definitions, eval);
}
// markov
if (option.getMarkov() != null)
{
Throw.when(!categorization.entails(GtuType.class), XmlParserException.class,
"The OD option Markov can only be used if GtuType is in the CATEGORY's.");
Throw.when(!categorization.entails(Lane.class) && option.getLane() != null, XmlParserException.class,
"Markov chains at lane level are not used if Lane's are not in the CATEGORY's.");
MarkovCorrelation<GtuType, Frequency> markov = new MarkovCorrelation<>();
for (State state : option.getMarkov().getState())
{
GtuType gtuType = definitions.get(GtuType.class, state.getGtuType().get(eval));
double correlation = state.getCorrelation().get(eval);
if (state.getParent() == null)
{
markov.addState(gtuType, correlation);
}
else
{
GtuType parentType = definitions.get(GtuType.class, state.getParent().get(eval));
markov.addState(parentType, gtuType, correlation);
}
}
setOption(odOptions, OdOptions.MARKOV, markov, option, otsNetwork, definitions, eval);
}
// lane biases
if (option.getLaneBiases() != null)
{
LaneBiases laneBiases = new LaneBiases();
for (org.opentrafficsim.xml.generated.LaneBias laneBiasType : option.getLaneBiases().getLaneBias())
{
String gtuTypeId = laneBiasType.getGtuType().get(eval);
GtuType gtuType = definitions.get(GtuType.class, gtuTypeId);
Throw.whenNull(gtuType, "GTU type %s in lane bias does not exist.", gtuTypeId);
laneBiases.addBias(gtuType, DefinitionsParser.parseLaneBias(laneBiasType, eval));
}
for (DefinedLaneBias definedLaneBias : option.getLaneBiases().getDefinedLaneBias())
{
String gtuTypeId = definedLaneBias.getGtuType().get(eval);
GtuType gtuType = definitions.get(GtuType.class, gtuTypeId);
Throw.whenNull(gtuType, "GTU type %s in defined lane bias does not exist.", gtuTypeId);
laneBiases.addBias(gtuType, definedLaneBiases.get(definedLaneBias.getGtuType().get(eval)));
}
setOption(odOptions, OdOptions.LANE_BIAS, laneBiases, option, otsNetwork, definitions, eval);
}
}
}
return odOptions;
}
/**
* Parse OD model option.
* @param otsNetwork RoadNetwork; network to obtain routes and lanes in categories.
* @param definitions Definitions; definitions to get GTU types in categories.
* @param factories Map<String, LaneBasedStrategicalPlannerFactory<?>>; parsed model factories.
* @param modelIdReferrals Map<String, String>; model id referrals.
* @param odOptions OdOptions; OD options.
* @param templates Set<GtuTemplate>; parsed GTU templates.
* @param defaultLmrsFactory LaneBasedStrategicalRoutePlannerFactory; default LMRS factory.
* @param option OdOptionItem; OD option item tag.
* @param eval Eval; expression evaluator.
* @throws XmlParserException when a non-existent model is referred.
*/
private static void parseModelOption(final RoadNetwork otsNetwork, final Definitions definitions,
final Map<String, LaneBasedStrategicalPlannerFactory<?>> factories, final Map<String, String> modelIdReferrals,
final OdOptions odOptions, final Set<GtuTemplate> templates,
final LaneBasedStrategicalRoutePlannerFactory defaultLmrsFactory, final OdOptionsItem option, final Eval eval)
throws XmlParserException
{
Factory characteristicsGeneratorFactory;
if (option.getDefaultModel() != null || (option.getModel() != null && !option.getModel().isEmpty()))
{
LaneBasedStrategicalPlannerFactory<?> defaultFactory;
if (option.getDefaultModel() != null)
{
// TODO: model id referral
String modelId = OdParser.getModelId(option.getDefaultModel(), modelIdReferrals, eval);
Throw.when(!factories.containsKey(modelId), XmlParserException.class,
"OD option DefaultModel refers to a non-existent model with ID %s.", modelId);
defaultFactory = factories.get(modelId);
}
else
{
defaultFactory = null;
}
// compose map that couples GTU types to factories through Model ID's
final Map<GtuType, LaneBasedStrategicalPlannerFactory<?>> gtuTypeFactoryMap = new LinkedHashMap<>();
if (option.getModel() != null)
{
for (Model model : option.getModel())
{
GtuType gtuType = definitions.get(GtuType.class, model.getGtuType().get(eval));
Throw.when(!factories.containsKey(model.getId().get(eval)), XmlParserException.class,
"OD option Model refers to a non existent-model with ID %s.", model.getId());
gtuTypeFactoryMap.put(gtuType, factories.get(getModelId(model, modelIdReferrals, eval)));
}
}
LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> factoryByGtuType =
new LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner>()
{
/** {@inheritDoc} */
@Override
public LaneBasedStrategicalPlanner create(final LaneBasedGtu gtu, final Route route, final Node origin,
final Node destination) throws GtuException
{
LaneBasedStrategicalPlannerFactory<?> strategicalPlannerFactory =
gtuTypeFactoryMap.get(gtu.getType());
if (strategicalPlannerFactory != null)
{
// a model factory for this GTU type is specified
return strategicalPlannerFactory.create(gtu, route, origin, destination);
}
if (defaultFactory != null)
{
// a default model factory is specified
return defaultFactory.create(gtu, route, origin, destination);
}
return defaultLmrsFactory.create(gtu, route, origin, destination);
}
};
characteristicsGeneratorFactory = new Factory(factoryByGtuType).setTemplates(templates);
setOption(odOptions, OdOptions.GTU_TYPE, characteristicsGeneratorFactory.create(), option, otsNetwork, definitions,
eval);
}
}
/**
* @param definitions Definitions; definitions to get GTU types in categories.
* @param gtuTemplates Map<String, org.opentrafficsim.xml.generated.GtuTemplate>; GTU template tags.
* @param streamMap StreamInformation; random streams.
* @param eval Eval; expression evaluator.
* @return GTU templates.
* @throws XmlParserException when a distribution cannot be parsed.
*/
private static Set<GtuTemplate> parseGtuTemplates(final Definitions definitions,
final Map<String, org.opentrafficsim.xml.generated.GtuTemplate> gtuTemplates, final StreamInformation streamMap,
final Eval eval) throws XmlParserException
{
Set<GtuTemplate> templates = new LinkedHashSet<>();
for (org.opentrafficsim.xml.generated.GtuTemplate template : gtuTemplates.values())
{
GtuType gtuType = definitions.get(GtuType.class, template.getGtuType().get(eval));
Generator<Length> lengthGenerator = ParseDistribution.parseContinuousDist(streamMap, template.getLengthDist(),
template.getLengthDist().getLengthUnit().get(eval), eval);
Generator<Length> widthGenerator = ParseDistribution.parseContinuousDist(streamMap, template.getWidthDist(),
template.getWidthDist().getLengthUnit().get(eval), eval);
Generator<Speed> maximumSpeedGenerator = ParseDistribution.parseContinuousDist(streamMap,
template.getMaxSpeedDist(), template.getMaxSpeedDist().getSpeedUnit().get(eval), eval);
if (template.getMaxAccelerationDist() == null || template.getMaxDecelerationDist() == null)
{
templates.add(new GtuTemplate(gtuType, lengthGenerator, widthGenerator, maximumSpeedGenerator));
}
else
{
Generator<Acceleration> maxAccelerationGenerator =
ParseDistribution.parseContinuousDist(streamMap, template.getMaxAccelerationDist(),
template.getMaxAccelerationDist().getAccelerationUnit().get(eval), eval);
Generator<Acceleration> maxDecelerationGenerator =
ParseDistribution.parseContinuousDist(streamMap, template.getMaxDecelerationDist(),
template.getMaxDecelerationDist().getAccelerationUnit().get(eval), eval);
templates.add(new GtuTemplate(gtuType, lengthGenerator, widthGenerator, maximumSpeedGenerator,
maxAccelerationGenerator, maxDecelerationGenerator));
}
}
return templates;
}
/**
* Parse the value of a LevelTimeType that specifies flow (i.e. with 'veh' per time unit).
* @param string String; value of LevelTimeType
* @param factor double; total applicable factor on this level
* @return Frequency; resulting frequency
*/
private static Frequency parseLevel(final String string, final double factor)
{
return Frequency.valueOf(string.replace("veh", "")).times(factor);
}
/**
* Sorts LevelTimeType in a list by the time value, if any.
* @param levelTime List<LevelTimeType>; sorted list
* @param eval Eval; expression evaluator.
*/
private static void sortLevelTime(final List<LevelTimeType> levelTime, final Eval eval)
{
Collections.sort(levelTime, new Comparator<LevelTimeType>()
{
/** {@inheritDoc} */
@Override
public int compare(final LevelTimeType o1, final LevelTimeType o2)
{
if (o1.getTime() == null && o2.getTime() == null)
{
return 0;
}
if (o1.getTime() == null)
{
return -1;
}
if (o2.getTime() == null)
{
return 1;
}
return o1.getTime().get(eval).compareTo(o2.getTime().get(eval));
}
});
}
/**
* Parse a list of {@code LevelTimeType} to a {@code TimeVector}.
* @param list List<LevelTimeType>; list of time information
* @param eval Eval; expression evaluator.
* @return TimeVector; time vector
* @throws XmlParserException if global time has no values
*/
private static TimeVector parseTimeVector(final List<LevelTimeType> list, final Eval eval) throws XmlParserException
{
List<Time> timeList = new ArrayList<>();
for (LevelTimeType time : list)
{
timeList.add(time.getTime().get(eval));
}
Collections.sort(timeList);
return new TimeVector(timeList, TimeUnit.DEFAULT);
}
/**
* Set option.
* @param odOptions OdOptions; OD options to set the option in
* @param option Option<T>; option to set
* @param value T; value to set the option to
* @param options OdOptionsItem; used to set the option on the right level (Link type, origin node, lane
* @param otsNetwork RoadNetwork; to get the link type, origin node or lane from
* @param definitions Definitions; parsed definitions.
* @param eval Eval; expression evaluator.
* @param <T> option value type
*/
private static <T> void setOption(final OdOptions odOptions, final Option<T> option, final T value,
final OdOptionsItem options, final RoadNetwork otsNetwork, final Definitions definitions, final Eval eval)
{
if (value != null)
{
if (options.getLinkType() != null)
{
odOptions.set(definitions.get(LinkType.class, options.getLinkType().get(eval)), option, value);
}
else if (options.getOrigin() != null)
{
odOptions.set(otsNetwork.getNode(options.getOrigin().get(eval)), option, value);
}
else if (options.getLane() != null)
{
CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(options.getLane().getLink().get(eval));
odOptions.set((Lane) link.getCrossSectionElement(options.getLane().getLane().get(eval)), option, value);
}
else
{
odOptions.set(option, value);
}
}
}
/**
* Returns the ID of a default model, referred if there is a referral specified.
* @param model String; model
* @param modelIdReferrals String; model ID
* @param eval Eval; expression evaluator.
* @return ID of a model, referred if there is a referral specified
*/
private static String getModelId(final DefaultModel model, final Map<String, String> modelIdReferrals, final Eval eval)
{
if (model.getModelIdReferral() != null)
{
return modelIdReferrals.get(model.getModelIdReferral().get(eval));
}
return model.getId().get(eval);
}
/**
* Returns the ID of a model, referred if there is a referral specified.
* @param model String; model
* @param modelIdReferrals String; model ID
* @param eval Eval; expression evaluator.
* @return ID of a model, referred if there is a referral specified
*/
private static String getModelId(final Model model, final Map<String, String> modelIdReferrals, final Eval eval)
{
if (model.getModelIdReferral() != null)
{
return modelIdReferrals.get(model.getModelIdReferral().get(eval));
}
return model.getId().get(eval);
}
}