LaneChange.java

  1. package org.opentrafficsim.road.gtu.lane.plan.operational;

  2. import java.io.Serializable;
  3. import java.util.ArrayList;
  4. import java.util.Iterator;
  5. import java.util.List;
  6. import java.util.Map;
  7. import java.util.Set;

  8. import org.djunits.value.vdouble.scalar.Acceleration;
  9. import org.djunits.value.vdouble.scalar.Duration;
  10. import org.djunits.value.vdouble.scalar.Length;
  11. import org.djunits.value.vdouble.scalar.Speed;
  12. import org.djutils.exceptions.Throw;
  13. import org.djutils.exceptions.Try;
  14. import org.opentrafficsim.base.parameters.ParameterTypes;
  15. import org.opentrafficsim.core.geometry.Bezier;
  16. import org.opentrafficsim.core.geometry.OTSGeometryException;
  17. import org.opentrafficsim.core.geometry.OTSLine3D;
  18. import org.opentrafficsim.core.geometry.OTSLine3D.FractionalFallback;
  19. import org.opentrafficsim.core.geometry.OTSPoint3D;
  20. import org.opentrafficsim.core.gtu.GTUException;
  21. import org.opentrafficsim.core.gtu.perception.EgoPerception;
  22. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
  23. import org.opentrafficsim.core.network.LateralDirectionality;
  24. import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
  25. import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
  26. import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
  27. import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;
  28. import org.opentrafficsim.road.network.lane.DirectedLanePosition;
  29. import org.opentrafficsim.road.network.lane.Lane;
  30. import org.opentrafficsim.road.network.lane.LaneDirection;

  31. import nl.tudelft.simulation.dsol.SimRuntimeException;
  32. import nl.tudelft.simulation.language.d3.DirectedPoint;

  33. /**
  34.  * Lane change status across operational plans. This class allows lane based tactical planners to perform lane changes without
  35.  * having to deal with many complexities concerning paths and lane registration. The main purpose of the tactical planner is to
  36.  * request a path using {@code getPath()} for each step of the tactical planner.
  37.  * <p>
  38.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  39.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
  40.  * <p>
  41.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 26, 2016 <br>
  42.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  43.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  44.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  45.  */
  46. public class LaneChange implements Serializable
  47. {

  48.     /** */
  49.     private static final long serialVersionUID = 20160811L;

  50.     /** Total lane change duration. */
  51.     private Duration desiredLaneChangeDuration;

  52.     /** Fraction of lane change had. */
  53.     private double fraction;

  54.     /** Boundary length within which the lane change should be performed. */
  55.     private Length boundary;

  56.     /** Lane change path. */
  57.     private LaneChangePath laneChangePath = LaneChangePath.SINE_INTERP;

  58.     /** Whether the GTU is changing lane. */
  59.     private LateralDirectionality laneChangeDirectionality = null;

  60.     /** Minimum lane change distance. */
  61.     private final Length minimumLaneChangeDistance;

  62.     /** Instance to invoke static method through scheduled event on. */
  63.     private static final LaneOperationalPlanBuilder BUILDER = new LaneOperationalPlanBuilder();

  64.     /** Minimum distance required to perform a lane change as factor on vehicle length. */
  65.     public static double MIN_LC_LENGTH_FACTOR = 1.5;

  66.     /**
  67.      * Constructor.
  68.      * @param gtu LaneBasedGTU; gtu
  69.      */
  70.     public LaneChange(final LaneBasedGTU gtu)
  71.     {
  72.         this.minimumLaneChangeDistance = gtu.getLength().times(MIN_LC_LENGTH_FACTOR);
  73.     }

  74.     /**
  75.      * Constructor.
  76.      * @param minimumLaneChangeDistance Length; minimum lane change distance
  77.      * @param desiredLaneChangeDuration Duration; deaired lane change duration
  78.      */
  79.     public LaneChange(final Length minimumLaneChangeDistance, final Duration desiredLaneChangeDuration)
  80.     {
  81.         this.minimumLaneChangeDistance = minimumLaneChangeDistance;
  82.         this.desiredLaneChangeDuration = desiredLaneChangeDuration;
  83.     }

  84.     /**
  85.      * Returns the minimum lane change distance.
  86.      * @return Length; minimum lane change distance
  87.      */
  88.     public Length getMinimumLaneChangeDistance()
  89.     {
  90.         return this.minimumLaneChangeDistance;
  91.     }

  92.     /**
  93.      * Sets the desired lane change duration. Should be set by a tactical planner.
  94.      * @param duration Duration; desired lane change duration
  95.      */
  96.     public void setDesiredLaneChangeDuration(final Duration duration)
  97.     {
  98.         this.desiredLaneChangeDuration = duration;
  99.     }

  100.     /**
  101.      * Sets the distance within which a lane change should be finished. Should be set by a tactical planner. In case of a single
  102.      * lane change required before some point, this is not required as the found center line length is intrinsically limited.
  103.      * For multiple lane changes being required, space after a lane change is required.
  104.      * @param boundary Length; boundary
  105.      */
  106.     public void setBoundary(final Length boundary)
  107.     {
  108.         this.boundary = boundary;
  109.     }

  110.     /**
  111.      * Returns the fraction of the lane change performed.
  112.      * @return double; fraction of lane change performed
  113.      */
  114.     public double getFraction()
  115.     {
  116.         return this.fraction;
  117.     }

  118.     /**
  119.      * Sets a lane change path.
  120.      * @param laneChangePath LaneChangePath; lane change path
  121.      */
  122.     public void setLaneChangePath(final LaneChangePath laneChangePath)
  123.     {
  124.         this.laneChangePath = laneChangePath;
  125.     }

  126.     /**
  127.      * Return whether the GTU is changing lane.
  128.      * @return whether the GTU is changing lane
  129.      */
  130.     public final boolean isChangingLane()
  131.     {
  132.         return this.laneChangeDirectionality != null;
  133.     }

  134.     /**
  135.      * Return whether the GTU is changing left.
  136.      * @return whether the GTU is changing left
  137.      */
  138.     public final boolean isChangingLeft()
  139.     {
  140.         return LateralDirectionality.LEFT.equals(this.laneChangeDirectionality);
  141.     }

  142.     /**
  143.      * Return whether the GTU is changing right.
  144.      * @return whether the GTU is changing right
  145.      */
  146.     public final boolean isChangingRight()
  147.     {
  148.         return LateralDirectionality.RIGHT.equals(this.laneChangeDirectionality);
  149.     }

  150.     /**
  151.      * Return lateral lane change direction.
  152.      * @return LateralDirectionality; lateral lane change direction
  153.      */
  154.     public final LateralDirectionality getDirection()
  155.     {
  156.         return this.laneChangeDirectionality;
  157.     }

  158.     /**
  159.      * Second lane of lane change relative to the reference lane. Note that the reference lane may either be the source or the
  160.      * target lane. Thus, the second lane during a lane change may either be the left or right lane, regardless of the lane
  161.      * change direction.
  162.      * @param gtu LaneBasedGTU; the GTU
  163.      * @return target lane of lane change
  164.      * @throws OperationalPlanException If no lane change is being performed.
  165.      */
  166.     public final RelativeLane getSecondLane(final LaneBasedGTU gtu) throws OperationalPlanException
  167.     {
  168.         Throw.when(!isChangingLane(), OperationalPlanException.class,
  169.                 "Target lane is requested, but no lane change is being performed.");
  170.         Map<Lane, Length> map;
  171.         DirectedLanePosition dlp;
  172.         try
  173.         {
  174.             map = gtu.positions(gtu.getReference());
  175.             dlp = gtu.getReferencePosition();
  176.         }
  177.         catch (GTUException exception)
  178.         {
  179.             throw new OperationalPlanException("Second lane of lane change could not be determined.", exception);
  180.         }
  181.         Set<Lane> accessibleLanes = dlp.getLane().accessibleAdjacentLanesPhysical(this.laneChangeDirectionality,
  182.                 gtu.getGTUType(), dlp.getGtuDirection());
  183.         if (!accessibleLanes.isEmpty() && map.containsKey(accessibleLanes.iterator().next()))
  184.         {
  185.             return isChangingLeft() ? RelativeLane.LEFT : RelativeLane.RIGHT;
  186.         }
  187.         return isChangingLeft() ? RelativeLane.RIGHT : RelativeLane.LEFT;
  188.     }

  189.     /**
  190.      * Returns the path for a lane change. Lane change initialization and finalization events are automatically performed.
  191.      * @param timeStep Duration; plan time step
  192.      * @param gtu LaneBasedGTU; gtu
  193.      * @param from DirectedLanePosition; current position on the from lane (i.e. not necessarily the reference position)
  194.      * @param startPosition DirectedPoint; current position in 2D
  195.      * @param planDistance Length; absolute distance that will be covered during the time step
  196.      * @param laneChangeDirection LateralDirectionality; lane change direction
  197.      * @return OTSLine3D; path
  198.      * @throws OTSGeometryException on path or shape error
  199.      */
  200.     public final OTSLine3D getPath(final Duration timeStep, final LaneBasedGTU gtu, final DirectedLanePosition from,
  201.             final DirectedPoint startPosition, final Length planDistance, final LateralDirectionality laneChangeDirection)
  202.             throws OTSGeometryException
  203.     {
  204.         // initiate lane change
  205.         boolean favoured = false;
  206.         if (!isChangingLane())
  207.         {
  208.             favoured = true;
  209.             this.laneChangeDirectionality = laneChangeDirection;
  210.             Try.execute(() -> gtu.initLaneChange(laneChangeDirection), "Error during lane change initialization.");
  211.         }
  212.         // Wouter: why would we ever not favor a side during a lane change
  213.         favoured = true;

  214.         // determine longitudinal distance along the from lanes
  215.         /*
  216.          * We take 3 factors in to account. The first two are 1) minimum physical lane change length, and 2) desired lane change
  217.          * duration. With the current mean speed of the plan, we take the maximum. So at very low speeds, the minimum physical
  218.          * length may increase the lane change duration. We also have 3) the maximum length before a lane change needs to have
  219.          * been performed. To overcome simulation troubles, we allow this to result in an even shorter length than the minimum
  220.          * physical distance. So: length = min( max("1", "2"), "3" ). These distances are all considered along the from-lanes.
  221.          * Actual path distance is different.
  222.          */
  223.         Speed meanSpeed = planDistance.divide(timeStep);
  224.         double minDuration = this.minimumLaneChangeDistance.si / meanSpeed.si;
  225.         double laneChangeDuration = Math.max(this.desiredLaneChangeDuration.si, minDuration);
  226.         if (this.boundary != null)
  227.         {
  228.             double maxDuration = this.boundary.si / meanSpeed.si;
  229.             laneChangeDuration = Math.min(laneChangeDuration, maxDuration);
  230.         }

  231.         double totalLength = laneChangeDuration * meanSpeed.si;
  232.         double fromDist = (1.0 - this.fraction) * totalLength; // remaining distance along from lanes to lane change end
  233.         Throw.when(fromDist < 0.0, RuntimeException.class, "Lane change results in negative distance along from lanes.");

  234.         // get fractional location there, build lane lists as we search over the distance
  235.         LaneDirection fromLane = from.getLaneDirection();
  236.         List<LaneDirection> fromLanes = new ArrayList<>();
  237.         List<LaneDirection> toLanes = new ArrayList<>();
  238.         fromLanes.add(fromLane);
  239.         toLanes.add(fromLane.getAdjacentLaneDirection(this.laneChangeDirectionality, gtu));
  240.         double endPosFrom = from.getPosition().si + fromDist;
  241.         while (endPosFrom + gtu.getFront().getDx().si > fromLane.getLane().getLength().si)
  242.         {
  243.             LaneDirection nextFromLane;
  244.             if (!favoured)
  245.             {
  246.                 nextFromLane = fromLane.getNextLaneDirection(gtu);
  247.             }
  248.             else
  249.             {
  250.                 Set<LaneDirection> nexts = fromLane.getNextForRoute(gtu);
  251.                 if (nexts != null && !nexts.isEmpty())
  252.                 {
  253.                     Iterator<LaneDirection> it = nexts.iterator();
  254.                     nextFromLane = it.next();
  255.                     while (it.hasNext())
  256.                     {
  257.                         nextFromLane =
  258.                                 LaneBasedTacticalPlanner.mostOnSide(nextFromLane, it.next(), this.laneChangeDirectionality);
  259.                     }
  260.                 }
  261.                 else
  262.                 {
  263.                     nextFromLane = null;
  264.                 }
  265.             }
  266.             if (nextFromLane == null)
  267.             {
  268.                 // there are no lanes to move on, restrict lane change length/duration (given fixed mean speed)
  269.                 double endFromPosLimit = fromLane.getLane().getLength().si - gtu.getFront().getDx().si;
  270.                 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
  271.                 laneChangeDuration *= f;
  272.                 endPosFrom = endFromPosLimit;
  273.                 break;
  274.             }
  275.             endPosFrom -= fromLane.getLane().getLength().si;
  276.             LaneDirection nextToLane = nextFromLane.getAdjacentLaneDirection(this.laneChangeDirectionality, gtu);
  277.             if (nextToLane == null)
  278.             {
  279.                 // there are no lanes to change to, restrict lane change length/duration (given fixed mean speed)
  280.                 double endFromPosLimit = fromLane.getLane().getLength().si - gtu.getFront().getDx().si;
  281.                 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
  282.                 laneChangeDuration *= f;
  283.                 endPosFrom = endFromPosLimit;
  284.                 break;
  285.             }
  286.             fromLane = nextFromLane;
  287.             fromLanes.add(fromLane);
  288.             toLanes.add(nextToLane);
  289.         }
  290.         // for long vehicles and short lanes, revert
  291.         while (endPosFrom < 0.0)
  292.         {
  293.             fromLanes.remove(fromLanes.size() - 1);
  294.             toLanes.remove(toLanes.size() - 1);
  295.             fromLane = fromLanes.get(fromLanes.size() - 1);
  296.             endPosFrom += fromLane.getLane().getLength().si;
  297.         }
  298.         // finally, get location at the final lane available
  299.         double endFractionalPositionFrom = fromLane.fractionAtCoveredDistance(Length.instantiateSI(endPosFrom));

  300.         DirectedLanePosition fromAdjusted = from;
  301.         while (fromAdjusted.getGtuDirection().isPlus() ? fromAdjusted.getPosition().gt(fromAdjusted.getLane().getLength())
  302.                 : fromAdjusted.getPosition().lt0())
  303.         {
  304.             // the from position is beyond the first lane (can occur if it is not the ref position)
  305.             fromLanes.remove(0);
  306.             toLanes.remove(0);
  307.             Length beyond = fromAdjusted.getGtuDirection().isPlus()
  308.                     ? fromAdjusted.getPosition().minus(fromAdjusted.getLane().getLength()) : fromAdjusted.getPosition().neg();
  309.             Length pos =
  310.                     fromLanes.get(0).getDirection().isPlus() ? beyond : fromLanes.get(0).getLane().getLength().minus(beyond);
  311.             fromAdjusted =
  312.                     Try.assign(() -> new DirectedLanePosition(fromLanes.get(0).getLane(), pos, fromLanes.get(0).getDirection()),
  313.                             OTSGeometryException.class, "Info for lane is null.");
  314.         }

  315.         // get path from shape

  316.         // create center lines
  317.         double startFractionalPositionFrom = from.getPosition().si / from.getLane().getLength().si;
  318.         OTSLine3D fromLine = getLine(fromLanes, startFractionalPositionFrom, endFractionalPositionFrom);
  319.         // project for toLane
  320.         double startFractionalPositionTo = toLanes.get(0).getLane().getCenterLine().projectFractional(null, null,
  321.                 startPosition.x, startPosition.y, FractionalFallback.ENDPOINT);
  322.         int last = fromLanes.size() - 1;
  323.         double frac = fromLanes.get(last).getDirection().isPlus() ? endFractionalPositionFrom : 1.0 - endFractionalPositionFrom;
  324.         DirectedPoint p = fromLanes.get(last).getLane().getCenterLine().getLocationFraction(frac);
  325.         double endFractionalPositionTo = toLanes.get(last).getLane().getCenterLine().projectFractional(null, null, p.x, p.y,
  326.                 FractionalFallback.ENDPOINT);
  327.         startFractionalPositionTo = startFractionalPositionTo >= 0.0 ? startFractionalPositionTo : 0.0;
  328.         endFractionalPositionTo = endFractionalPositionTo <= 1.0 ? endFractionalPositionTo : 1.0;
  329.         endFractionalPositionTo = endFractionalPositionTo <= 0.0 ? endFractionalPositionFrom : endFractionalPositionTo;
  330.         // check for poor projection (end location is difficult to project; we have no path yet so we use the from lane)
  331.         if (fromLanes.size() == 1 && endFractionalPositionTo <= startFractionalPositionTo)
  332.         {
  333.             endFractionalPositionTo = Math.min(Math.max(startFractionalPositionTo + 0.001, endFractionalPositionFrom), 1.0);
  334.         }
  335.         if (startFractionalPositionTo >= 1.0)
  336.         {
  337.             toLanes.remove(0);
  338.             startFractionalPositionTo = 0.0;
  339.         }
  340.         OTSLine3D toLine = getLine(toLanes, startFractionalPositionTo, endFractionalPositionTo);

  341.         OTSLine3D path = this.laneChangePath.getPath(timeStep, planDistance, meanSpeed, fromAdjusted, startPosition,
  342.                 laneChangeDirection, fromLine, toLine, Duration.instantiateSI(laneChangeDuration), this.fraction);
  343.         // update
  344.         // TODO: this assumes the time step will not be interrupted
  345.         this.fraction += timeStep.si / laneChangeDuration; // the total fraction this step increases

  346.         // deal with lane change end
  347.         double requiredLength = planDistance.si - path.getLength().si;
  348.         if (requiredLength > 0.0 || this.fraction > 0.999)
  349.         {
  350.             try
  351.             {
  352.                 gtu.getSimulator().scheduleEventNow(gtu, BUILDER, "scheduleLaneChangeFinalization",
  353.                         new Object[] { gtu, Length.min(planDistance, path.getLength()), laneChangeDirection });
  354.             }
  355.             catch (SimRuntimeException exception)
  356.             {
  357.                 throw new RuntimeException("Error during lane change finalization.", exception);
  358.             }
  359.             // add length to path on to lanes
  360.             if (requiredLength > 0.0)
  361.             {
  362.                 LaneDirection toLane = toLanes.get(toLanes.size() - 1);
  363.                 int n = path.size();
  364.                 // ignore remainder of first lane if fraction is at the end of the lane
  365.                 if (0.0 < endFractionalPositionFrom && endFractionalPositionFrom < 1.0)
  366.                 {
  367.                     OTSLine3D remainder = toLane.getDirection().isPlus()
  368.                             ? toLane.getLane().getCenterLine().extractFractional(endFractionalPositionTo, 1.0)
  369.                             : toLane.getLane().getCenterLine().extractFractional(0.0, endFractionalPositionTo).reverse();
  370.                     path = OTSLine3D.concatenate(0.001, path, remainder);
  371.                     requiredLength = planDistance.si - path.getLength().si;
  372.                 }
  373.                 // add further lanes
  374.                 while (toLane != null && requiredLength > 0.0)
  375.                 {
  376.                     toLane = toLane.getNextLaneDirection(gtu);
  377.                     if (toLane != null) // let's hope we will move on to a sink
  378.                     {
  379.                         OTSLine3D remainder = toLane.getDirection().isPlus() ? toLane.getLane().getCenterLine()
  380.                                 : toLane.getLane().getCenterLine().reverse();
  381.                         path = OTSLine3D.concatenate(Lane.MARGIN.si, path, remainder);
  382.                         requiredLength = planDistance.si - path.getLength().si + Lane.MARGIN.si;
  383.                     }
  384.                 }
  385.                 // filter near-duplicate point which results in projection exceptions
  386.                 if (this.fraction > 0.999) // this means point 'target' is essentially at the design line
  387.                 {
  388.                     OTSPoint3D[] points = new OTSPoint3D[path.size() - 1];
  389.                     System.arraycopy(path.getPoints(), 0, points, 0, n - 1);
  390.                     System.arraycopy(path.getPoints(), n, points, n - 1, path.size() - n);
  391.                     path = new OTSLine3D(points);
  392.                 }
  393.             }
  394.             // reset lane change
  395.             this.laneChangeDirectionality = null;
  396.             this.boundary = null;
  397.             this.fraction = 0.0;
  398.         }

  399.         return path;
  400.     }

  401.     /**
  402.      * Returns a line from the lane center lines, cutting of at the from position and the end fractional position.
  403.      * @param lanes List&lt;LaneDirection&gt;; lanes
  404.      * @param startFractionalPosition double; current fractional GTU position on first lane
  405.      * @param endFractionalPosition double; target fractional GTU position on last lane
  406.      * @return OTSLine3D; line from the lane center lines
  407.      * @throws OTSGeometryException on fraction outside of range
  408.      */
  409.     private OTSLine3D getLine(final List<LaneDirection> lanes, final double startFractionalPosition,
  410.             final double endFractionalPosition) throws OTSGeometryException
  411.     {
  412.         OTSLine3D line = null;
  413.         for (LaneDirection lane : lanes)
  414.         {
  415.             if (line == null && lane.equals(lanes.get(lanes.size() - 1)))
  416.             {
  417.                 if (lane.getDirection().isPlus())
  418.                 {
  419.                     line = lane.getLane().getCenterLine().extractFractional(startFractionalPosition, endFractionalPosition);
  420.                 }
  421.                 else
  422.                 {
  423.                     line = lane.getLane().getCenterLine().extractFractional(startFractionalPosition, endFractionalPosition)
  424.                             .reverse();
  425.                 }
  426.             }
  427.             else if (line == null)
  428.             {
  429.                 if (lane.getDirection().isPlus())
  430.                 {
  431.                     line = lane.getLane().getCenterLine().extractFractional(startFractionalPosition, 1.0);
  432.                 }
  433.                 else
  434.                 {
  435.                     line = lane.getLane().getCenterLine().extractFractional(0.0, startFractionalPosition).reverse();
  436.                 }
  437.             }
  438.             else if (lane.equals(lanes.get(lanes.size() - 1)))
  439.             {
  440.                 if (lane.getDirection().isPlus())
  441.                 {
  442.                     line = OTSLine3D.concatenate(Lane.MARGIN.si, line,
  443.                             lane.getLane().getCenterLine().extractFractional(0.0, endFractionalPosition));
  444.                 }
  445.                 else
  446.                 {
  447.                     line = OTSLine3D.concatenate(Lane.MARGIN.si, line,
  448.                             lane.getLane().getCenterLine().extractFractional(endFractionalPosition, 1.0).reverse());
  449.                 }
  450.             }
  451.             else
  452.             {
  453.                 if (lane.getDirection().isPlus())
  454.                 {
  455.                     line = OTSLine3D.concatenate(Lane.MARGIN.si, line, lane.getLane().getCenterLine());
  456.                 }
  457.                 else
  458.                 {
  459.                     line = OTSLine3D.concatenate(Lane.MARGIN.si, line, lane.getLane().getCenterLine().reverse());
  460.                 }
  461.             }
  462.         }
  463.         return line;
  464.     }

  465.     /**
  466.      * Checks whether the given GTU has sufficient space relative to a {@code Headway}.
  467.      * @param gtu LaneBasedGTU; gtu
  468.      * @param headway Headway; headway
  469.      * @return boolean; whether the given GTU has sufficient space relative to a {@code Headway}
  470.      */
  471.     public boolean checkRoom(final LaneBasedGTU gtu, final Headway headway)
  472.     {
  473.         if (this.desiredLaneChangeDuration == null)
  474.         {
  475.             this.desiredLaneChangeDuration = Try.assign(() -> gtu.getParameters().getParameter(ParameterTypes.LCDUR),
  476.                     "LaneChange; the desired lane change duration should be set or paramater LCDUR should be defined.");
  477.         }

  478.         EgoPerception<?, ?> ego = gtu.getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(EgoPerception.class);
  479.         Speed egoSpeed = ego == null ? gtu.getSpeed() : ego.getSpeed();
  480.         Acceleration egoAcceleration = ego == null ? gtu.getAcceleration() : ego.getAcceleration();
  481.         Speed speed = headway.getSpeed() == null ? Speed.ZERO : headway.getSpeed();
  482.         Acceleration acceleration = headway.getAcceleration() == null ? Acceleration.ZERO : headway.getAcceleration();
  483.         Length s0 = gtu.getParameters().getParameterOrNull(ParameterTypes.S0);
  484.         s0 = s0 == null ? Length.ZERO : s0;

  485.         Length distanceToStop;
  486.         if (speed.eq0())
  487.         {
  488.             distanceToStop = Length.ZERO;
  489.         }
  490.         else
  491.         {
  492.             Acceleration b = gtu.getParameters().getParameterOrNull(ParameterTypes.B);
  493.             b = b == null ? Acceleration.ZERO : b.neg();
  494.             Acceleration a = Acceleration.min(Acceleration.max(b, acceleration.plus(b)), acceleration);
  495.             if (a.ge0())
  496.             {
  497.                 return true;
  498.             }
  499.             double t = speed.si / -a.si;
  500.             distanceToStop = Length.instantiateSI(speed.si * t + .5 * a.si * t * t);
  501.         }

  502.         Length availableDistance = headway.getDistance().plus(distanceToStop);
  503.         double t = this.desiredLaneChangeDuration.si;
  504.         if (egoAcceleration.lt0())
  505.         {
  506.             t = Math.min(egoSpeed.si / -egoAcceleration.si, t);
  507.         }
  508.         Length requiredDistance =
  509.                 Length.max(Length.instantiateSI(egoSpeed.si * t + .5 * egoAcceleration.si * t * t), this.minimumLaneChangeDistance)
  510.                         .plus(s0);
  511.         return availableDistance.gt(requiredDistance);
  512.     }

  513.     /** {@inheritDoc} */
  514.     @Override
  515.     public String toString()
  516.     {
  517.         return "LaneChange [fraction=" + this.fraction + ", laneChangeDirectionality=" + this.laneChangeDirectionality + "]";
  518.     }

  519.     /**
  520.      * Provides a (partial) path during lane changes.
  521.      * <p>
  522.      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  523.      * <br>
  524.      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  525.      * <p>
  526.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 30 apr. 2018 <br>
  527.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  528.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  529.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  530.      */
  531.     public interface LaneChangePath
  532.     {

  533.         /** Sine-shaped interpolation between center lines. */
  534.         LaneChangePath SINE_INTERP = new InterpolatedLaneChangePath()
  535.         {
  536.             /** {@inheritDoc} */
  537.             @Override
  538.             double longitudinalFraction(final double lateralFraction)
  539.             {
  540.                 return 1.0 - Math.acos(2.0 * (lateralFraction - 0.5)) / Math.PI;
  541.             }

  542.             /** {@inheritDoc} */
  543.             @Override
  544.             double lateralFraction(final double longitudinalFraction)
  545.             {
  546.                 return 0.5 - 0.5 * Math.cos(longitudinalFraction * Math.PI);
  547.             }
  548.         };

  549.         /** Linear interpolation between center lines. */
  550.         LaneChangePath LINEAR = new InterpolatedLaneChangePath()
  551.         {

  552.             @Override
  553.             double longitudinalFraction(final double lateralFraction)
  554.             {
  555.                 return lateralFraction;
  556.             }

  557.             @Override
  558.             double lateralFraction(final double longitudinalFraction)
  559.             {
  560.                 return longitudinalFraction;
  561.             }
  562.         };

  563.         /** A simple Bezier curve directly to the lane change target position. */
  564.         LaneChangePath BEZIER = new LaneChangePath()
  565.         {
  566.             /** {@inheritDoc} */
  567.             @Override
  568.             public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
  569.                     final DirectedLanePosition from, final DirectedPoint startPosition,
  570.                     final LateralDirectionality laneChangeDirection, final OTSLine3D fromLine, final OTSLine3D toLine,
  571.                     final Duration laneChangeDuration, final double lcFraction) throws OTSGeometryException
  572.             {
  573.                 DirectedPoint target = toLine.getLocationFraction(1.0);
  574.                 return Bezier.cubic(64, startPosition, target, 0.5);
  575.             }
  576.         };

  577.         /** The target point (including rotation) for the coming time step is based on a sine wave. */
  578.         LaneChangePath SINE = new SequentialLaneChangePath()
  579.         {
  580.             /** {@inheritDoc} */
  581.             @Override
  582.             protected double lateralFraction(final double lcFraction)
  583.             {
  584.                 return -1.0 / (2 * Math.PI) * Math.sin(2 * Math.PI * lcFraction) + lcFraction;
  585.             }

  586.             /** {@inheritDoc} */
  587.             @Override
  588.             protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
  589.             {
  590.                 return Math.atan((-width * Math.cos(2 * Math.PI * cumulLcLength / totalLcLength) / totalLcLength)
  591.                         + width / totalLcLength);
  592.             }
  593.         };

  594.         /** The target point (including rotation) for the coming time step is based on a 3rd-degree polynomial. */
  595.         LaneChangePath POLY3 = new SequentialLaneChangePath()
  596.         {
  597.             /** {@inheritDoc} */
  598.             @Override
  599.             protected double lateralFraction(final double lcFraction)
  600.             {
  601.                 return 3 * (lcFraction * lcFraction) - 2 * (lcFraction * lcFraction * lcFraction);
  602.             }

  603.             /** {@inheritDoc} */
  604.             @Override
  605.             protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
  606.             {
  607.                 return Math.atan(cumulLcLength * 6 * width / (totalLcLength * totalLcLength)
  608.                         - cumulLcLength * cumulLcLength * 6 * width / (totalLcLength * totalLcLength * totalLcLength));
  609.             }
  610.         };

  611.         /**
  612.          * A helper class to allow a lane change to follow a sequential determination of the target position (including
  613.          * rotation) for each time step.
  614.          * <p>
  615.          * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
  616.          * reserved. <br>
  617.          * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  618.          * <p>
  619.          * @version $Revision$, $LastChangedDate$, by $Author$, initial version 30 apr. 2018 <br>
  620.          * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  621.          * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  622.          * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  623.          */
  624.         abstract class SequentialLaneChangePath implements LaneChangePath
  625.         {
  626.             /** {@inheritDoc} */
  627.             @Override
  628.             public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
  629.                     final DirectedLanePosition from, final DirectedPoint startPosition,
  630.                     final LateralDirectionality laneChangeDirection, final OTSLine3D fromLine, final OTSLine3D toLine,
  631.                     final Duration laneChangeDuration, final double lcFraction) throws OTSGeometryException
  632.             {
  633.                 DirectedPoint toTarget = toLine.getLocationFraction(1.0);
  634.                 DirectedPoint fromTarget = fromLine.getLocationFraction(1.0);
  635.                 double width = laneChangeDirection.isRight() ? fromTarget.distance(toTarget) : -fromTarget.distance(toTarget);
  636.                 double dFraction = timeStep.si / laneChangeDuration.si;
  637.                 return getPathRecursive(planDistance, meanSpeed, 1.0, width, from, startPosition, fromLine, toLine,
  638.                         laneChangeDuration, lcFraction, dFraction);
  639.             }

  640.             /**
  641.              * Attempts to derive a path. If the resulting path is shorter than {@code planDistance} (e.g. lane change towards
  642.              * the inside of a curve), this method calls itself using a larger look-ahead distance.
  643.              * @param planDistance Length; plan distance
  644.              * @param meanSpeed Speed; mean speed during plan
  645.              * @param buffer double; buffer factor to assure sufficient path length is found, increased recursively
  646.              * @param width double; lateral deviation from from lanes at lane change end
  647.              * @param from DirectedLanePosition; current position on the from-lanes
  648.              * @param startPosition DirectedPoint; current 2D position
  649.              * @param fromLine OTSLine3D; from line
  650.              * @param toLine OTSLine3D; to line
  651.              * @param laneChangeDuration Duration; current considered duration of the entire lane change
  652.              * @param lcFraction double; lane change fraction at beginning of the plan
  653.              * @param dFraction double; additional lane change fraction to be made during the plan
  654.              * @return OTSLine3D a (partial) path for a lane change
  655.              * @throws OTSGeometryException on wrong fractional position
  656.              */
  657.             private OTSLine3D getPathRecursive(final Length planDistance, final Speed meanSpeed, final double buffer,
  658.                     final double width, final DirectedLanePosition from, final DirectedPoint startPosition,
  659.                     final OTSLine3D fromLine, final OTSLine3D toLine, final Duration laneChangeDuration,
  660.                     final double lcFraction, final double dFraction) throws OTSGeometryException
  661.             {
  662.                 // factor on path length to not overshoot a fraction of 1.0 in lane change progress, i.e. <1 if lane change will
  663.                 // be finished in the coming time step
  664.                 double cutoff = (1.0 - lcFraction) / (dFraction * buffer);
  665.                 cutoff = cutoff > 1.0 ? 1.0 : cutoff;

  666.                 // lane change fraction at end of plan
  667.                 double lcFractionEnd = lcFraction + dFraction * buffer * cutoff;

  668.                 // lateral fraction at that point according to shape
  669.                 double f = lateralFraction(lcFractionEnd);

  670.                 // from-lane length
  671.                 double totalLcLength = meanSpeed.si * laneChangeDuration.si;
  672.                 double cumulLcLength = totalLcLength * lcFractionEnd;

  673.                 // TODO: sequential is disabled as LaneChangePath now uses 2 OTSLine3D's instead of 2 List<Lane>'s. This was
  674.                 // done as the code using LaneChangePath (i.e. LaneChange) required more details on fractional positions itself.
  675.                 return null;
  676.                 /*-
  677.                 // find lane we will end up on at the end of the plan
  678.                 double positionAtEnd = (from.getGtuDirection().isPlus() ? from.getPosition().si
  679.                         : from.getLane().getLength().si - from.getPosition().si) + planDistance.si * buffer * cutoff;
  680.                 for (int i = 0; i < fromLanes.size(); i++)
  681.                 {
  682.                     LaneDirection fromLane = fromLanes.get(i);
  683.                     if (fromLane.getLength().si >= positionAtEnd)
  684.                     {
  685.                         // get target point by interpolation between from and to lane
  686.                         double endFraction = fromLane.fractionAtCoveredDistance(Length.instantiateSI(positionAtEnd));
  687.                         DirectedPoint pFrom = fromLane.getLocationFraction(endFraction);
  688.                         DirectedPoint pTo = toLanes.get(i).getLocationFraction(endFraction);
  689.                         DirectedPoint target = new DirectedPoint((1 - f) * pFrom.x + f * pTo.x, (1 - f) * pFrom.y + f * pTo.y,
  690.                                 (1 - f) * pFrom.z + f * pTo.z);
  691.                         // set rotation according to shape, relative to lane center line
  692.                         target.setRotZ(
  693.                                 (1 - f) * pFrom.getRotZ() + f * pTo.getRotZ() - angle(width, cumulLcLength, totalLcLength));
  694.                         // Bezier path towards that point
  695.                         OTSLine3D path = Bezier.cubic(64, startPosition, target, 0.5);
  696.                         // check if long enough, otherwise look further (e.g. changing to inside of curve gives a shorter path)
  697.                         if (path.getLength().si < planDistance.si && cutoff == 1.0)
  698.                         {
  699.                             return getPathRecursive(planDistance, meanSpeed, buffer * 1.25, width, from, startPosition,
  700.                                     fromLine, toLine, laneChangeDuration, lcFraction, dFraction);
  701.                         }
  702.                         return path;
  703.                     }
  704.                     positionAtEnd -= fromLane.getLength().si;
  705.                 }
  706.                 Throw.when(lcFraction + dFraction < 0.999, RuntimeException.class,
  707.                         "No partial path for lane change could be determined; fromLanes are too short.");
  708.                 return null;
  709.                 */
  710.             }

  711.             /**
  712.              * Returns the fractional lateral deviation given a fraction of lane change being completed.
  713.              * @param lcFraction double; fraction of lane change
  714.              * @return double; lateral deviation
  715.              */
  716.             protected abstract double lateralFraction(double lcFraction);

  717.             /**
  718.              * Returns the angle, relative to the lane center line, at the given cumulative length for a lane change of given
  719.              * total length and lateral deviation.
  720.              * @param width double; lateral deviation from from lanes at lane change end
  721.              * @param cumulLcLength double; cumulative length (along from lanes) covered so far
  722.              * @param totalLcLength double; total (along from lanes) length to cover in lane change
  723.              * @return double; angle, relative to the lane center line
  724.              */
  725.             protected abstract double angle(double width, double cumulLcLength, double totalLcLength);
  726.         }

  727.         /**
  728.          * Helper class for interpolation between the from and to center lines.
  729.          * <p>
  730.          * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
  731.          * reserved. <br>
  732.          * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  733.          * <p>
  734.          * @version $Revision$, $LastChangedDate$, by $Author$, initial version May 3, 2019 <br>
  735.          * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  736.          * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  737.          * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  738.          */
  739.         abstract class InterpolatedLaneChangePath implements LaneChangePath
  740.         {

  741.             /** {@inheritDoc} */
  742.             @Override
  743.             public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
  744.                     final DirectedLanePosition from, final DirectedPoint startPosition,
  745.                     final LateralDirectionality laneChangeDirection, final OTSLine3D fromLine, final OTSLine3D toLine,
  746.                     final Duration laneChangeDuration, final double lcFraction) throws OTSGeometryException
  747.             {

  748.                 double dx = fromLine.get(0).getLocation().x - startPosition.x;
  749.                 double dy = fromLine.get(0).getLocation().y - startPosition.y;
  750.                 double distFromLoc = Math.sqrt(dx * dx + dy * dy);
  751.                 dx = fromLine.get(0).getLocation().x - toLine.get(0).getLocation().x;
  752.                 dy = fromLine.get(0).getLocation().y - toLine.get(0).getLocation().y;
  753.                 double distFromTo = Math.sqrt(dx * dx + dy * dy);
  754.                 double startLateralFraction = distFromLoc / distFromTo;
  755.                 // Location is not on path in z-direction, so using .distance() create bugs
  756.                 // PK: added test for NaN (which occurs when fromLine and toLine start on top of each other.
  757.                 if (Double.isNaN(startLateralFraction) || startLateralFraction > 1.0)
  758.                 {
  759.                     startLateralFraction = 1.0;
  760.                 }
  761.                 double startLongitudinalFractionTotal = longitudinalFraction(startLateralFraction);

  762.                 double nSegments = Math.ceil((64 * (1.0 - lcFraction)));
  763.                 List<OTSPoint3D> pointList = new ArrayList<>();
  764.                 double zStart = (1.0 - startLateralFraction) * fromLine.get(0).z + startLateralFraction * toLine.get(0).z;
  765.                 pointList.add(new OTSPoint3D(startPosition.x, startPosition.y, zStart));
  766.                 for (int i = 1; i <= nSegments; i++)
  767.                 {
  768.                     double f = i / nSegments;
  769.                     double longitudinalFraction = startLongitudinalFractionTotal + f * (1.0 - startLongitudinalFractionTotal);
  770.                     double lateralFraction = lateralFraction(longitudinalFraction);
  771.                     double lateralFractionInv = 1.0 - lateralFraction;
  772.                     DirectedPoint fromPoint = fromLine.getLocationFraction(f);
  773.                     DirectedPoint toPoint = toLine.getLocationFraction(f);
  774.                     pointList.add(new OTSPoint3D(lateralFractionInv * fromPoint.x + lateralFraction * toPoint.x,
  775.                             lateralFractionInv * fromPoint.y + lateralFraction * toPoint.y,
  776.                             lateralFractionInv * fromPoint.z + lateralFraction * toPoint.z));
  777.                 }

  778.                 OTSLine3D line = new OTSLine3D(pointList);
  779.                 // clean line for projection inconsistencies (position -> center lines -> interpolated new position)
  780.                 double angleChange = Math.abs(line.getLocation(Length.ZERO).getRotZ() - startPosition.getRotZ());
  781.                 int i = 1;
  782.                 while (angleChange > Math.PI / 4)
  783.                 {
  784.                     i++;
  785.                     if (i >= pointList.size() - 2)
  786.                     {
  787.                         // return original if we can't clean the line, perhaps extreme road curvature or line with 2 points
  788.                         return new OTSLine3D(pointList);
  789.                     }
  790.                     List<OTSPoint3D> newPointList = new ArrayList<>(pointList.subList(i, pointList.size()));
  791.                     newPointList.add(0, pointList.get(0));
  792.                     line = new OTSLine3D(newPointList);
  793.                     angleChange = Math.abs(line.getLocation(Length.ZERO).getRotZ() - startPosition.getRotZ());
  794.                 }
  795.                 return line;
  796.             }

  797.             /**
  798.              * Transform lateral to longitudinal fraction.
  799.              * @param lateralFraction double; lateral fraction
  800.              * @return double; transformation of lateral to longitudinal fraction
  801.              */
  802.             abstract double longitudinalFraction(double lateralFraction);

  803.             /**
  804.              * Transform longitudinal to lateral fraction.
  805.              * @param longitudinalFraction double; longitudinal fraction
  806.              * @return double; transformation of longitudinal to lateral fraction
  807.              */
  808.             abstract double lateralFraction(double longitudinalFraction);

  809.         }

  810.         /**
  811.          * Returns a (partial) path for a lane change. The method is called both at the start and during a lane change, and
  812.          * should return a valid path. This path should at least have a length of {@code planDistance}, unless the lane change
  813.          * will be finished during the coming time step. In that case, the caller of this method is to lengthen the path along
  814.          * the center line of the target lane.
  815.          * @param timeStep Duration; time step
  816.          * @param planDistance Length; distance covered during the operational plan
  817.          * @param meanSpeed Speed; mean speed during time step
  818.          * @param from DirectedLanePosition; current position on the from-lanes
  819.          * @param startPosition DirectedPoint; current 2D position
  820.          * @param laneChangeDirection LateralDirectionality; lane change direction
  821.          * @param fromLine OTSLine3D; from line
  822.          * @param toLine OTSLine3D; to line
  823.          * @param laneChangeDuration Duration; current considered duration of the entire lane change
  824.          * @param lcFraction double; fraction of lane change done so far
  825.          * @return OTSLine3D a (partial) path for a lane change
  826.          * @throws OTSGeometryException on wrong fractional position
  827.          */
  828.         OTSLine3D getPath(Duration timeStep, Length planDistance, Speed meanSpeed, DirectedLanePosition from,
  829.                 DirectedPoint startPosition, LateralDirectionality laneChangeDirection, OTSLine3D fromLine, OTSLine3D toLine,
  830.                 Duration laneChangeDuration, double lcFraction) throws OTSGeometryException;
  831.     }
  832. }