DemandParser.java

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

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.djunits.unit.SpeedUnit;
import org.djunits.unit.Unit;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Frequency;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.LinearDensity;
import org.djunits.value.vdouble.scalar.Speed;
import org.djunits.value.vdouble.scalar.base.DoubleScalarRel;
import org.djutils.eval.Eval;
import org.djutils.exceptions.Throw;
import org.jgrapht.alg.interfaces.AStarAdmissibleHeuristic;
import org.opentrafficsim.base.parameters.ParameterException;
import org.opentrafficsim.core.definitions.Definitions;
import org.opentrafficsim.core.distributions.Distribution;
import org.opentrafficsim.core.distributions.Distribution.FrequencyAndObject;
import org.opentrafficsim.core.distributions.Generator;
import org.opentrafficsim.core.distributions.ProbabilityException;
import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
import org.opentrafficsim.core.gtu.GtuType;
import org.opentrafficsim.core.idgenerator.IdGenerator;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.LinkWeight;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.core.network.route.FixedRouteGenerator;
import org.opentrafficsim.core.network.route.ProbabilisticRouteGenerator;
import org.opentrafficsim.core.network.route.Route;
import org.opentrafficsim.core.units.distributions.ContinuousDistDoubleScalar;
import org.opentrafficsim.road.gtu.generator.GeneratorPositions;
import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator;
import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator.RoomChecker;
import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuTemplate;
import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuTemplateDistribution;
import org.opentrafficsim.road.gtu.generator.headway.HeadwayGenerator;
import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlannerFactory;
import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModelFactory;
import org.opentrafficsim.road.gtu.lane.tactical.following.IdmPlus;
import org.opentrafficsim.road.gtu.lane.tactical.following.IdmPlusFactory;
import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLmrsPerceptionFactory;
import org.opentrafficsim.road.gtu.lane.tactical.lmrs.Lmrs;
import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LmrsFactory;
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.LanePosition;
import org.opentrafficsim.road.network.lane.object.detector.DetectorType;
import org.opentrafficsim.road.network.lane.object.detector.SinkDetector;
import org.opentrafficsim.xml.bindings.types.StringType;
import org.opentrafficsim.xml.generated.ConstantDistType;
import org.opentrafficsim.xml.generated.Demand;
import org.opentrafficsim.xml.generated.GtuTemplate;
import org.opentrafficsim.xml.generated.GtuTemplateMix;
import org.opentrafficsim.xml.generated.RouteMix;
import org.opentrafficsim.xml.generated.ShortestRoute;
import org.opentrafficsim.xml.generated.ShortestRoute.Cost;
import org.opentrafficsim.xml.generated.ShortestRouteMix;
import org.opentrafficsim.xml.generated.Sink;

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

/**
 * This utility class parses all demand related elements that are <i>not</i> from an OD.
 * <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" target="_blank">Alexander Verbraeck</a>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 */
public final class DemandParser
{
    /** */
    private DemandParser()
    {
        // utility class
    }

    /**
     * Parse the ROUTE tags.
     * @param otsNetwork RoadNetwork; the network to insert the parsed objects in
     * @param definitions Definitions; parsed definitions
     * @param demand Demand; the Demand tag
     * @param eval Eval; expression evaluator.
     * @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
     */
    static void parseRoutes(final RoadNetwork otsNetwork, final Definitions definitions, final Demand demand, final Eval eval)
            throws NetworkException
    {
        for (org.opentrafficsim.xml.generated.Route routeTag : demand.getRoute())
        {
            String gtuTypeString = routeTag.getGtuType().get(eval);
            GtuType gtuType = definitions.get(GtuType.class, gtuTypeString);
            Route route = new Route(routeTag.getId(), gtuType);
            Throw.when(gtuType == null, NetworkException.class, "GtuType %s not found in Route %s", gtuTypeString,
                    routeTag.getId());
            for (StringType nodeTag : routeTag.getNode())
            {
                Node node = otsNetwork.getNode(nodeTag.get(eval));
                Throw.when(node == null, NetworkException.class, "Node %s not found in Route %s", nodeTag, routeTag.getId());
                route.addNode(node);
            }
            otsNetwork.addRoute(gtuType, route);
        }
    }

    /**
     * Parse the ShortestRoute tags.
     * @param otsNetwork RoadNetwork; the network to insert the parsed objects in
     * @param definitions Definitions; parsed definitions
     * @param demand Demand; the Demand tag
     * @param eval Eval; expression evaluator.
     * @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
     */
    static void parseShortestRoutes(final RoadNetwork otsNetwork, final Definitions definitions, final Demand demand,
            final Eval eval) throws NetworkException
    {
        for (ShortestRoute shortestRouteTag : demand.getShortestRoute())
        {
            String gtuTypeId = shortestRouteTag.getGtuType().get(eval);
            GtuType gtuType = definitions.get(GtuType.class, gtuTypeId);
            Throw.when(gtuType == null, NetworkException.class, "GtuType %s not found in ShortestRoute %s",
                    shortestRouteTag.getGtuType(), shortestRouteTag.getId());
            Route route = new Route(shortestRouteTag.getId(), gtuType);
            String nodeFromId = shortestRouteTag.getFrom().get(eval);
            Node nodeFrom = otsNetwork.getNode(nodeFromId);
            Throw.when(nodeFrom == null, NetworkException.class, "From Node %s not found in ShortestRoute", nodeFromId,
                    shortestRouteTag.getId());
            String nodeToId = shortestRouteTag.getTo().get(eval);
            Node nodeTo = otsNetwork.getNode(nodeToId);
            Throw.when(nodeTo == null, NetworkException.class, "To Node %s not found in ShortestRoute", nodeToId,
                    shortestRouteTag.getId());
            List<Node> nodesVia = new ArrayList<>();
            for (StringType nodeViaValue : shortestRouteTag.getVia())
            {
                String nodeViaId = nodeViaValue.get(eval);
                Node nodeVia = otsNetwork.getNode(nodeViaId);
                Throw.when(nodeTo == null, NetworkException.class, "Via Node %s not found in ShortestRoute", nodeViaId,
                        shortestRouteTag.getId());
                nodesVia.add(nodeVia);
            }

            LinkWeight linkWeight;
            Cost cost = shortestRouteTag.getCost();
            if (cost == null || cost.getDistance() != null)
            {
                // Default link weight / standard distance weight
                linkWeight = LinkWeight.ASTAR_LENGTH_NO_CONNECTORS;
            }
            else if (cost.getFreeFlowTime() != null)
            {
                // Free flow time
                Speed maxSpeed = new Speed(250.0, SpeedUnit.KM_PER_HOUR);
                AStarAdmissibleHeuristic<Node> aStarHeuristicTime = getTimeAStarHeuristic(maxSpeed);
                linkWeight = new LinkWeight()
                {
                    /** {@inheritDoc} */
                    @Override
                    public double getWeight(final Link link)
                    {
                        if (link.isConnector())
                        {
                            return 1000000;
                        }
                        Speed speedLimit = link instanceof CrossSectionLink
                                ? getLinkSpeedLimit((CrossSectionLink) link, gtuType) : maxSpeed;
                        return link.getLength().si / speedLimit.si;
                    }

                    /** {@inheritDoc} */
                    @Override
                    public AStarAdmissibleHeuristic<Node> getAStarHeuristic()
                    {
                        return aStarHeuristicTime;
                    }
                };
            }
            else if (cost.getDistanceAndFreeFlowTime() != null)
            {
                // Balance time and distance
                LinearDensity perDistance = cost.getDistanceAndFreeFlowTime().getDistanceCost().get(eval);
                Frequency perTime = cost.getDistanceAndFreeFlowTime().getTimeCost().get(eval);
                Speed maxSpeed = new Speed(250.0, SpeedUnit.KM_PER_HOUR);
                AStarAdmissibleHeuristic<Node> aStarHeuristicTime = getTimeAStarHeuristic(maxSpeed);
                linkWeight = new LinkWeight()
                {
                    /** {@inheritDoc} */
                    @Override
                    public double getWeight(final Link link)
                    {
                        if (link.isConnector())
                        {
                            return 1000000;
                        }
                        Speed speedLimit = link instanceof CrossSectionLink
                                ? getLinkSpeedLimit((CrossSectionLink) link, gtuType) : maxSpeed;
                        return link.getLength().si * perDistance.si + (link.getLength().si / speedLimit.si) * perTime.si;
                    }

                    /** {@inheritDoc} */
                    @Override
                    public AStarAdmissibleHeuristic<Node> getAStarHeuristic()
                    {
                        return new AStarAdmissibleHeuristic<Node>()
                        {
                            /** {@inheritDoc} */
                            @Override
                            public double getCostEstimate(final Node sourceVertex, final Node targetVertex)
                            {
                                return EUCLIDEAN_DISTANCE.getCostEstimate(sourceVertex, targetVertex) * perDistance.si
                                        + aStarHeuristicTime.getCostEstimate(sourceVertex, targetVertex) * perTime.si;
                            }
                        };
                    }
                };
            }
            else
            {
                throw new NetworkException("Shortest route " + shortestRouteTag.getId() + " has invalid cost defined.");
            }

            Route shortestRoute = otsNetwork.getShortestRouteBetween(gtuType, nodeFrom, nodeTo, nodesVia, linkWeight);
            Throw.when(shortestRoute == null, NetworkException.class, "Cannot find shortest route from %s to %s",
                    nodeFrom.getId(), nodeTo.getId());
            for (Node node : shortestRoute.getNodes())
            {
                route.addNode(node);
            }
            otsNetwork.addRoute(gtuType, route);
        }
    }

    /**
     * Returns the speed limit representative for the link. This is the highest speed limit defined on any lane for the GTU
     * type, or the maximum speed limit for any GTU type if no speed limit is defined for the given GTU type.
     * @param link CrossSectionLink; link.
     * @param gtuType GtuType; GTU type.
     * @return Speed; speed limit representative for the link
     */
    private static Speed getLinkSpeedLimit(final CrossSectionLink link, final GtuType gtuType)
    {
        Speed speed = null;
        for (Lane lane : link.getLanes())
        {
            if (lane.getType().isCompatible(gtuType))
            {
                try
                {
                    Speed laneSpeed = lane.getSpeedLimit(gtuType);
                    speed = speed == null || laneSpeed.gt(speed) ? laneSpeed : speed;
                }
                catch (NetworkException e)
                {
                    // just skip
                }
            }
        }
        if (speed == null)
        {
            for (Lane lane : link.getLanes())
            {
                if (lane.getType().isCompatible(gtuType))
                {
                    try
                    {
                        Speed laneSpeed = lane.getHighestSpeedLimit();
                        speed = speed == null || laneSpeed.gt(speed) ? laneSpeed : speed;
                    }
                    catch (NetworkException e)
                    {
                        // just skip
                    }
                }
            }
        }
        return speed;
    }

    /**
     * Returns an A* heuristic that divides the euclidean distance by a maximum speed.
     * @param maxSpeed Speed; maximum speed any GTU could reasonable have.
     * @return A* heuristic that divides the euclidean distance by a maximum speed
     */
    private static AStarAdmissibleHeuristic<Node> getTimeAStarHeuristic(final Speed maxSpeed)
    {
        return new AStarAdmissibleHeuristic<Node>()
        {
            /** {@inheritDoc} */
            @Override
            public double getCostEstimate(final Node sourceVertex, final Node targetVertex)
            {
                return LinkWeight.EUCLIDEAN_DISTANCE.getCostEstimate(sourceVertex, targetVertex) / maxSpeed.si;
            }
        };
    }

    /**
     * Parse the RouteMix tags.
     * @param otsNetwork RoadNetwork; the network to insert the parsed objects in
     * @param demand Demand; the Demand tag
     * @param eval Eval; expression evaluator.
     * @return id-based Map of routemix objects as FrequencyAndObject lists
     * @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
     */
    static Map<String, List<FrequencyAndObject<Route>>> parseRouteMix(final RoadNetwork otsNetwork, final Demand demand,
            final Eval eval) throws NetworkException
    {
        Map<String, List<FrequencyAndObject<Route>>> routeMixMap = new LinkedHashMap<>();
        for (RouteMix routeMixTag : demand.getRouteMix())
        {
            List<FrequencyAndObject<Route>> probRoutes = new ArrayList<>();
            for (RouteMix.Route mixRoute : routeMixTag.getRoute())
            {
                String routeName = mixRoute.getId().get(eval);
                double weight = mixRoute.getWeight().get(eval);
                Route route = otsNetwork.getRoute(routeName);
                Throw.when(route == null, NetworkException.class, "Parsing RouteMix %s -- Route %s not found",
                        routeMixTag.getId(), routeName);
                probRoutes.add(new FrequencyAndObject<>(weight, route));
            }
            routeMixMap.put(routeMixTag.getId(), probRoutes);
        }
        return routeMixMap;
    }

    /**
     * Parse the ShortestRouteMix tags.
     * @param otsNetwork RoadNetwork; the network to insert the parsed objects in
     * @param demand Demand; the Demand tag
     * @param eval Eval; expression evaluator.
     * @return id-based Map of routemix objects as FrequencyAndObject lists
     * @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
     */
    static Map<String, List<FrequencyAndObject<Route>>> parseShortestRouteMix(final RoadNetwork otsNetwork, final Demand demand,
            final Eval eval) throws NetworkException
    {
        Map<String, List<FrequencyAndObject<Route>>> shortestRouteMixMap = new LinkedHashMap<>();
        for (ShortestRouteMix routeMixTag : demand.getShortestRouteMix())
        {
            List<FrequencyAndObject<Route>> probRoutes = new ArrayList<>();
            for (ShortestRouteMix.ShortestRoute mixRoute : routeMixTag.getShortestRoute())
            {
                String routeName = mixRoute.getId().get(eval);
                double weight = mixRoute.getWeight().get(eval);
                Route route = otsNetwork.getRoute(routeName);
                Throw.when(route == null, NetworkException.class, "Parsing ShortestRouteMix %s -- ShortestRoute %s not found",
                        routeMixTag.getId(), routeName);
                probRoutes.add(new FrequencyAndObject<>(weight, route));
            }
            shortestRouteMixMap.put(routeMixTag.getId(), probRoutes);
        }
        return shortestRouteMixMap;
    }

    /**
     * Parse the Generators.
     * @param otsNetwork RoadNetwork; the network to insert the parsed objects in
     * @param definitions Definitions; parsed definitions
     * @param demand Network; the Network tag
     * @param gtuTemplates GtuTemplate tags
     * @param routeMixMap map with route mix entries
     * @param shortestRouteMixMap map with shortest route mix entries
     * @param streamInformation map with stream information
     * @param eval Eval; expression evaluator.
     * @return list of created GTU generators
     * @throws XmlParserException when the objects cannot be inserted into the network due to inconsistencies
     */
    public static List<LaneBasedGtuGenerator> parseGenerators(final RoadNetwork otsNetwork, final Definitions definitions,
            final Demand demand, final Map<String, GtuTemplate> gtuTemplates,
            final Map<String, List<FrequencyAndObject<Route>>> routeMixMap,
            final Map<String, List<FrequencyAndObject<Route>>> shortestRouteMixMap, final StreamInformation streamInformation,
            final Eval eval) throws XmlParserException
    {
        OtsSimulatorInterface simulator = otsNetwork.getSimulator();
        List<LaneBasedGtuGenerator> generators = new ArrayList<>();
        try
        {
            for (org.opentrafficsim.xml.generated.Generator generatorTag : demand.getGenerator())
            {
                StreamInterface stream = ParseUtil.findStream(streamInformation, generatorTag.getRandomStream(), eval);

                String linkId = generatorTag.getLink().get(eval);
                String laneId = generatorTag.getLane().get(eval);

                Generator<Route> routeGenerator;
                if (generatorTag.getRoute() != null)
                {
                    String routeId = generatorTag.getRoute().get(eval);
                    Route route = otsNetwork.getRoute(routeId);
                    Throw.when(route == null, XmlParserException.class, "Generator for Lane %s.%s: Route %s not found", linkId,
                            laneId, routeId);
                    routeGenerator = new FixedRouteGenerator(route);
                }

                else if (generatorTag.getRouteMix() != null)
                {
                    String routeMixId = generatorTag.getRouteMix().get(eval);
                    List<FrequencyAndObject<Route>> routeMix = routeMixMap.get(routeMixId);
                    Throw.when(routeMix == null, XmlParserException.class, "Generator for Lane %s.%s: RouteMix %s not found",
                            linkId, laneId, routeMixId);
                    RouteMix routeMixXml = null;
                    for (RouteMix mix : demand.getRouteMix())
                    {
                        if (mix.getId().equals(routeMixId))
                        {
                            routeMixXml = mix;
                        }
                    }
                    Throw.when(routeMixXml == null, XmlParserException.class, "Route mix '%s' not defined.", routeMixId);
                    StreamInterface routeMixStream =
                            ParseUtil.findStream(streamInformation, routeMixXml.getRandomStream(), eval);
                    try
                    {
                        routeGenerator = new ProbabilisticRouteGenerator(routeMix, routeMixStream);
                    }
                    catch (ProbabilityException exception)
                    {
                        throw new RuntimeException(
                                "Generator for Lane " + linkId + "." + laneId + "Could not generate RouteMix " + routeMixId);
                    }
                }

                else if (generatorTag.getShortestRoute() != null)
                {
                    String shortestRouteId = generatorTag.getShortestRoute().get(eval);
                    Route shortestRoute = otsNetwork.getRoute(shortestRouteId);
                    Throw.when(shortestRoute == null, XmlParserException.class,
                            "Generator for Lane %s.%s: ShortestRoute %s not found", linkId, laneId, shortestRouteId);
                    routeGenerator = new FixedRouteGenerator(shortestRoute);
                }

                else if (generatorTag.getShortestRouteMix() != null)
                {
                    String shortestRouteMixId = generatorTag.getShortestRouteMix().get(eval);
                    List<FrequencyAndObject<Route>> shortestRouteMix = shortestRouteMixMap.get(shortestRouteMixId);
                    Throw.when(shortestRouteMix == null, XmlParserException.class,
                            "Generator for Lane %s.%s: ShortestRouteMix %s not found", linkId, laneId, shortestRouteMixId);
                    ShortestRouteMix shortestRouteMixXml = null;
                    for (ShortestRouteMix mix : demand.getShortestRouteMix())
                    {
                        if (mix.getId().equals(shortestRouteMixId))
                        {
                            shortestRouteMixXml = mix;
                        }
                    }
                    Throw.when(shortestRouteMixXml == null, XmlParserException.class, "Shortest route mix '%s' not defined.",
                            shortestRouteMixId);
                    StreamInterface shortestRouteMixStream =
                            ParseUtil.findStream(streamInformation, shortestRouteMixXml.getRandomStream(), eval);
                    try
                    {
                        routeGenerator = new ProbabilisticRouteGenerator(shortestRouteMix, shortestRouteMixStream);
                    }
                    catch (ProbabilityException exception)
                    {
                        throw new RuntimeException("Generator for Lane " + linkId + "." + laneId
                                + "Could not generate ShortestRouteMix " + shortestRouteMixId);
                    }
                }

                else
                {
                    throw new XmlParserException("Generator for Lane " + linkId + "." + laneId + ": No route information");
                }

                CarFollowingModelFactory<IdmPlus> idmPlusFactory = new IdmPlusFactory(stream);
                LaneBasedTacticalPlannerFactory<Lmrs> tacticalFactory =
                        new LmrsFactory(idmPlusFactory, new DefaultLmrsPerceptionFactory());
                LaneBasedStrategicalRoutePlannerFactory strategicalFactory =
                        new LaneBasedStrategicalRoutePlannerFactory(tacticalFactory);

                // the distribution of GTUs
                Distribution<LaneBasedGtuTemplate> gtuTypeDistribution;
                if (generatorTag.getGtuTemplate() != null)
                {
                    gtuTypeDistribution = new Distribution<>(stream);
                    String gtuTemplateId = generatorTag.getGtuTemplate().get(eval);
                    GtuTemplate templateTag = gtuTemplates.get(gtuTemplateId);
                    Throw.when(templateTag == null, XmlParserException.class, "GtuTemplate %s in generator not defined",
                            gtuTemplateId);
                    LaneBasedGtuTemplate templateGtuType = parseGtuTemplate(templateTag, definitions, streamInformation,
                            gtuTemplateId, routeGenerator, strategicalFactory, eval);
                    gtuTypeDistribution.add(new FrequencyAndObject<>(1.0, templateGtuType));
                }
                else if (generatorTag.getGtuTemplateMix() != null)
                {
                    String gtuTemplateMixId = generatorTag.getGtuTemplateMix().get(eval);
                    Throw.when(demand.getGtuTemplateMix() == null, XmlParserException.class,
                            "GtuTemplateMix %s cannot be found, there are no mixes defined.", gtuTemplateMixId);
                    GtuTemplateMix gtuTemplateMix = null;
                    for (GtuTemplateMix mix : demand.getGtuTemplateMix())
                    {
                        if (gtuTemplateMixId.equals(mix.getId()))
                        {
                            gtuTemplateMix = mix;
                            break;
                        }
                    }
                    Throw.when(gtuTemplateMix == null, XmlParserException.class, "GtuTemplateMix %s is not defined.",
                            gtuTemplateMixId);
                    StreamInterface mixStream = gtuTemplateMix.getRandomStream() == null ? stream
                            : ParseUtil.findStream(streamInformation, gtuTemplateMix.getRandomStream(), eval);
                    gtuTypeDistribution = new Distribution<>(mixStream);
                    for (org.opentrafficsim.xml.generated.GtuTemplateMix.GtuTemplate template : gtuTemplateMix.getGtuTemplate())
                    {
                        Throw.when(!gtuTemplates.containsKey(template.getId()), XmlParserException.class,
                                "GtuTemplate %s is not defined.", template.getId());
                        LaneBasedGtuTemplate templateGtuType = parseGtuTemplate(gtuTemplates.get(template.getId()), definitions,
                                streamInformation, gtuTemplateMixId, routeGenerator, strategicalFactory, eval);
                        gtuTypeDistribution.add(
                                new FrequencyAndObject<LaneBasedGtuTemplate>(template.getWeight().get(eval), templateGtuType));
                    }
                }
                else
                {
                    throw new XmlParserException("No GTU information in Generator");
                }

                RoomChecker roomChecker = ParseUtil.parseRoomChecker(generatorTag.getRoomChecker(), eval);

                Generator<Duration> headwayGenerator = new HeadwayGenerator(generatorTag.getFrequency().get(eval), stream);

                CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(linkId);
                Lane lane = (Lane) link.getCrossSectionElement(laneId);
                Length position = ParseUtil.parseLengthBeginEnd(generatorTag.getPosition().get(eval), lane.getLength());
                Set<LanePosition> initialLongitudinalPositions = new LinkedHashSet<>();
                initialLongitudinalPositions.add(new LanePosition(lane, position));

                IdGenerator idGenerator = new IdGenerator(lane.getFullId());

                LaneBasedGtuTemplateDistribution characteristicsGenerator =
                        new LaneBasedGtuTemplateDistribution(gtuTypeDistribution);
                generators.add(new LaneBasedGtuGenerator(lane.getFullId(), headwayGenerator, characteristicsGenerator,
                        GeneratorPositions.create(initialLongitudinalPositions, stream), otsNetwork, simulator, roomChecker,
                        idGenerator));
            }
        }
        catch (Exception exception)
        {
            throw new XmlParserException(exception);
        }
        return generators;
    }

    /**
     * Parse a GtuTemplate.
     * @param templateTag GtuTemplate; tag of the GTU template.
     * @param definitions Definitions; definitions.
     * @param streamInformation StreamInformation; stream information.
     * @param gtuTemplateId String; id of GTU template.
     * @param routeGenerator Generator&lt;Route&gt;; route generator.
     * @param strategicalFactory LaneBasedStrategicalRoutePlannerFactory; strategical factory.
     * @param eval Eval; expression evaluator.
     * @return LaneBasedGtuTemplate; parsed GTU template.
     * @throws XmlParserException if the GtuType is not defined.
     */
    private static LaneBasedGtuTemplate parseGtuTemplate(final GtuTemplate templateTag, final Definitions definitions,
            final StreamInformation streamInformation, final String gtuTemplateId, final Generator<Route> routeGenerator,
            final LaneBasedStrategicalRoutePlannerFactory strategicalFactory, final Eval eval) throws XmlParserException
    {
        String gtuTypeId = templateTag.getGtuType().get(eval);
        GtuType gtuType = definitions.get(GtuType.class, gtuTypeId);
        Throw.when(gtuType == null, XmlParserException.class, "GtuType %s in GtuTemplate %s not defined", gtuTypeId,
                gtuTemplateId);
        Generator<Length> lengthGenerator = makeGenerator(streamInformation, templateTag.getLengthDist(),
                templateTag.getLengthDist().getLengthUnit().get(eval), eval);
        Generator<Length> widthGenerator = makeGenerator(streamInformation, templateTag.getWidthDist(),
                templateTag.getWidthDist().getLengthUnit().get(eval), eval);
        Generator<Speed> maximumSpeedGenerator = makeGenerator(streamInformation, templateTag.getMaxSpeedDist(),
                templateTag.getMaxSpeedDist().getSpeedUnit().get(eval), eval);
        LaneBasedGtuTemplate templateGtuType = new LaneBasedGtuTemplate(gtuType, lengthGenerator, widthGenerator,
                maximumSpeedGenerator, strategicalFactory, routeGenerator);
        return templateGtuType;
    }

    /**
     * Parse a unit-based distribution into a Generator.
     * @param streamMap StreamInformation; the map with predefined streams
     * @param distribution ConstantDistType; the tag to parse (sub class of ConstantDistType)
     * @param unit U; unit as taken from the tag
     * @param eval Eval; expression evaluator.
     * @return the generator
     * @throws XmlParserException on parse error
     */
    private static <T extends DoubleScalarRel<U, T>, U extends Unit<U>> Generator<T> makeGenerator(
            final StreamInformation streamMap, final ConstantDistType distribution, final U unit, final Eval eval)
            throws XmlParserException
    {
        try
        {
            final ContinuousDistDoubleScalar.Rel<T, U> dist =
                    ParseDistribution.parseContinuousDist(streamMap, distribution, unit, eval);
            Generator<T> generator = new Generator<T>()
            {
                /** {@inheritDoc} */
                @Override
                public T draw() throws ProbabilityException, ParameterException
                {
                    return dist.draw();
                }

                /** {@inheritDoc} */
                @Override
                public String toString()
                {
                    return "Generator<>(" + dist.getDistribution().toString() + " " + dist.getDisplayUnit() + ")";
                }
            };
            return generator;
        }
        catch (Exception exception)
        {
            throw new XmlParserException(exception);
        }
    }

    /**
     * Parse the Sinks.
     * @param otsNetwork RoadNetwork; the network to insert the parsed objects in
     * @param demand Network; the Network tag
     * @param simulator OtsSimulatorInterface; the simulator
     * @param definitions Definitions; type definitions.
     * @param eval Eval; expression evaluator.
     * @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
     */
    public static void parseSinks(final RoadNetwork otsNetwork, final Demand demand, final OtsSimulatorInterface simulator,
            final Definitions definitions, final Eval eval) throws NetworkException
    {
        for (Sink sinkTag : demand.getSink())
        {
            CrossSectionLink link = (CrossSectionLink) otsNetwork.getLink(sinkTag.getLink().get(eval));
            Lane lane = (Lane) link.getCrossSectionElement(sinkTag.getLane().get(eval));
            Length position = ParseUtil.parseLengthBeginEnd(sinkTag.getPosition().get(eval), lane.getLength());
            DetectorType detectorType = definitions.get(DetectorType.class, sinkTag.getType().get(eval));
            if (sinkTag.getDestination().get(eval))
            {
                new SinkDetector(lane, position, simulator, detectorType, SinkDetector.DESTINATION);
            }
            else
            {
                new SinkDetector(lane, position, simulator, detectorType);
            }
        }
    }

}