View Javadoc
1   package org.opentrafficsim.road.gtu.lane.plan.operational;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.Iterator;
6   import java.util.List;
7   import java.util.Map;
8   
9   import org.djunits.unit.LengthUnit;
10  import org.djunits.value.vdouble.scalar.Acceleration;
11  import org.djunits.value.vdouble.scalar.Duration;
12  import org.djunits.value.vdouble.scalar.Length;
13  import org.djunits.value.vdouble.scalar.Speed;
14  import org.djunits.value.vdouble.scalar.Time;
15  import org.djutils.draw.point.OrientedPoint2d;
16  import org.djutils.draw.point.Point2d;
17  import org.djutils.exceptions.Throw;
18  import org.djutils.logger.CategoryLogger;
19  import org.opentrafficsim.base.parameters.ParameterException;
20  import org.opentrafficsim.core.geometry.OtsGeometryException;
21  import org.opentrafficsim.core.geometry.OtsLine2d;
22  import org.opentrafficsim.core.gtu.GtuException;
23  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
24  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
25  import org.opentrafficsim.core.gtu.plan.operational.Segment;
26  import org.opentrafficsim.core.gtu.plan.operational.Segments;
27  import org.opentrafficsim.core.network.LateralDirectionality;
28  import org.opentrafficsim.core.network.NetworkException;
29  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
30  import org.opentrafficsim.road.network.lane.Lane;
31  import org.opentrafficsim.road.network.lane.LanePosition;
32  import org.opentrafficsim.road.network.lane.object.detector.LaneDetector;
33  import org.opentrafficsim.road.network.lane.object.detector.SinkDetector;
34  
35  import nl.tudelft.simulation.dsol.SimRuntimeException;
36  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
37  
38  /**
39   * Builder for several often used operational plans. E.g., decelerate to come to a full stop at the end of a shape; accelerate
40   * to reach a certain speed at the end of a curve; drive constant on a curve; decelerate or accelerate to reach a given end
41   * speed at the end of a curve, etc.<br>
42   * TODO driving with negative speeds (backward driving) is not yet supported.
43   * <p>
44   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
45   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
46   * </p>
47   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
48   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
49   */
50  public final class LaneOperationalPlanBuilder
51  {
52  
53      /**
54       * Minimum distance of an operational plan path; anything shorter will be truncated to 0. <br>
55       * If objects related to e.g. molecular movements are simulated using this code, a setter for this parameter will be needed.
56       */
57      private static final Length MINIMUM_CREDIBLE_PATH_LENGTH = new Length(0.001, LengthUnit.METER);
58  
59      /** Constructor. */
60      LaneOperationalPlanBuilder()
61      {
62          // class should not be instantiated
63      }
64  
65      /**
66       * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
67       * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
68       * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
69       * before completing the path, a truncated path that ends where the GTU stops is used instead.
70       * @param gtu LaneBasedGtu; the GTU for debugging purposes
71       * @param startTime Time; the current time or a time in the future when the plan should start
72       * @param startSpeed Speed; the speed at the start of the path
73       * @param acceleration Acceleration; the acceleration to use
74       * @param timeStep Duration; time step for the plan
75       * @param deviative boolean; whether the plan is deviative
76       * @return the operational plan to accomplish the given end speed
77       * @throws OperationalPlanException when the construction of the operational path fails
78       * @throws OtsGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
79       *             first lane
80       */
81      public static LaneBasedOperationalPlan buildAccelerationPlan(final LaneBasedGtu gtu, final Time startTime,
82              final Speed startSpeed, final Acceleration acceleration, final Duration timeStep, final boolean deviative)
83              throws OperationalPlanException, OtsGeometryException
84      {
85          Segments segments = Segments.off(startSpeed, timeStep, acceleration);
86          Length distance = Length.ZERO;
87          for (Segment segment : segments.getSegments())
88          {
89              distance = distance.plus(segment.totalDistance());
90          }
91  
92          if (startSpeed.si <= OperationalPlan.DRIFTING_SPEED_SI && acceleration.le(Acceleration.ZERO)
93                  || distance.le(MINIMUM_CREDIBLE_PATH_LENGTH))
94          {
95              OrientedPoint2d point = gtu.getLocation();
96              Point2d p2 = new Point2d(point.x + Math.cos(point.getDirZ()), point.y + Math.sin(point.getDirZ()));
97              OtsLine2d path = new OtsLine2d(point, p2);
98              return new LaneBasedOperationalPlan(gtu, path, startTime, Segments.standStill(timeStep), deviative);
99          }
100 
101         OtsLine2d path = createPathAlongCenterLine(gtu, distance);
102         return new LaneBasedOperationalPlan(gtu, path, startTime, segments, deviative);
103     }
104 
105     /**
106      * Creates a path along lane center lines.
107      * @param gtu LaneBasedGtu; gtu
108      * @param distance Length; minimum distance
109      * @return OtsLine2d; path along lane center lines
110      * @throws OtsGeometryException when any of the OtsLine2d operations fails
111      */
112     public static OtsLine2d createPathAlongCenterLine(final LaneBasedGtu gtu, final Length distance) throws OtsGeometryException
113     {
114         OtsLine2d path = null;
115         try
116         {
117             LanePosition ref = gtu.getReferencePosition();
118             double f = ref.lane().fraction(ref.position());
119             if (f < 1.0)
120             {
121                 if (f >= 0.0)
122                 {
123                     path = ref.lane().getCenterLine().extractFractional(f, 1.0);
124                 }
125                 else
126                 {
127                     path = ref.lane().getCenterLine().extractFractional(0.0, 1.0);
128                 }
129             }
130             Lane prevFrom = null;
131             Lane from = ref.lane();
132             Length prevPos = null;
133             Length pos = ref.position();
134             int n = 1;
135             while (path == null || path.getLength().si < distance.si + n * Lane.MARGIN.si)
136             {
137                 n++;
138                 prevFrom = from;
139                 if (null == from)
140                 {
141                     CategoryLogger.always().warn("About to die: GTU {} has null from value", gtu.getId());
142                 }
143                 from = gtu.getNextLaneForRoute(from);
144                 // if (from != null && from.getType().equals(Lane.SHOULDER))
145                 // {
146                 // CategoryLogger.always().warn("GTU {} on link {} will move on to shoulder.", gtu.getId(),
147                 // ref.getLane().getLink().getId());
148                 // }
149                 prevPos = pos;
150                 pos = Length.ZERO;
151                 if (from == null)
152                 {
153                     // check sink detector
154                     for (LaneDetector detector : prevFrom.getDetectors(prevPos, prevFrom.getLength(), gtu.getType()))
155                     {
156                         if (detector instanceof SinkDetector && ((SinkDetector) detector).willDestroy(gtu))
157                         {
158                             // just add some length so the GTU is happy to go to the sink
159                             OrientedPoint2d end = path.getLocationExtendedSI(distance.si + n * Lane.MARGIN.si);
160                             List<Point2d> points = new ArrayList<>(Arrays.asList(path.getPoints()));
161                             points.add(end);
162                             return new OtsLine2d(points);
163                         }
164                     }
165                     // force lane change, and create path from there
166                     for (LateralDirectionality lat : new LateralDirectionality[] {LateralDirectionality.LEFT,
167                             LateralDirectionality.RIGHT})
168                     {
169                         Lane latLane = prevFrom.getAdjacentLane(lat, gtu.getType());
170                         if (latLane != null && gtu.getNextLaneForRoute(latLane) != null)
171                         {
172                             gtu.changeLaneInstantaneously(lat);
173                             CategoryLogger.always().warn("GTU {} on link {} is forced to change lane towards {}", gtu.getId(),
174                                     ref.lane().getLink().getId(), lat);
175                             return createPathAlongCenterLine(gtu, distance);
176                         }
177                     }
178                     CategoryLogger.always().error("GTU {} on link {} has nowhere to go and no sink detector either",
179                             gtu.getId(), ref.lane().getLink().getId());
180                     gtu.destroy();
181                     return path;
182                 }
183                 if (path == null)
184                 {
185                     path = from.getCenterLine();
186                 }
187                 else
188                 {
189                     path = OtsLine2d.concatenate(Lane.MARGIN.si, path, from.getCenterLine());
190                 }
191             }
192         }
193         catch (GtuException exception)
194         {
195             throw new RuntimeException("Error during creation of path.", exception);
196         }
197         return path;
198     }
199 
200     /**
201      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
202      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
203      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
204      * before completing the path, a truncated path that ends where the GTU stops is used instead.
205      * @param gtu LaneBasedGtu; the GTU for debugging purposes
206      * @param laneChangeDirectionality LateralDirectionality; direction of lane change (on initiation only, after that not
207      *            important)
208      * @param startPosition OrientedPoint2d; current position
209      * @param startTime Time; the current time or a time in the future when the plan should start
210      * @param startSpeed Speed; the speed at the start of the path
211      * @param acceleration Acceleration; the acceleration to use
212      * @param timeStep Duration; time step for the plan
213      * @param laneChange LaneChange; lane change status
214      * @return the operational plan to accomplish the given end speed
215      * @throws OperationalPlanException when the construction of the operational path fails
216      * @throws OtsGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
217      *             first lane
218      */
219     @SuppressWarnings("checkstyle:parameternumber")
220     public static LaneBasedOperationalPlan buildAccelerationLaneChangePlan(final LaneBasedGtu gtu,
221             final LateralDirectionality laneChangeDirectionality, final OrientedPoint2d startPosition, final Time startTime,
222             final Speed startSpeed, final Acceleration acceleration, final Duration timeStep, final LaneChange laneChange)
223             throws OperationalPlanException, OtsGeometryException
224     {
225 
226         // on first call during lane change, use laneChangeDirectionality as laneChange.getDirection() is NONE
227         // on successive calls, use laneChange.getDirection() as laneChangeDirectionality is NONE (i.e. no LC initiated)
228         LateralDirectionality direction = laneChange.isChangingLane() ? laneChange.getDirection() : laneChangeDirectionality;
229 
230         Segments segments = Segments.off(startSpeed, timeStep, acceleration);
231         Length distance = Length.ZERO;
232         for (Segment segment : segments.getSegments())
233         {
234             distance = distance.plus(segment.totalDistance());
235         }
236 
237         try
238         {
239             // get position on from lane
240             Map<Lane, Length> positions = gtu.positions(gtu.getReference());
241             LanePosition ref = gtu.getReferencePosition();
242             Iterator<Lane> iterator = ref.lane().accessibleAdjacentLanesPhysical(direction, gtu.getType()).iterator();
243             Lane adjLane = iterator.hasNext() ? iterator.next() : null;
244             LanePosition from = null;
245             if (laneChange.getDirection() == null || (adjLane != null && positions.containsKey(adjLane)))
246             {
247                 // reference lane is from lane, this is ok
248                 from = ref;
249             }
250             else
251             {
252                 // reference lane is to lane, this should be accounted for
253                 for (Lane lane : positions.keySet())
254                 {
255                     if (lane.accessibleAdjacentLanesPhysical(direction, gtu.getType()).contains(ref.lane()))
256                     {
257                         from = new LanePosition(lane, positions.get(lane));
258                         break;
259                     }
260                 }
261             }
262             Throw.when(from == null, RuntimeException.class, "From lane could not be determined during lane change.");
263 
264             // get path and make plan
265             OtsLine2d path = laneChange.getPath(timeStep, gtu, from, startPosition, distance, direction);
266             LaneBasedOperationalPlan plan = new LaneBasedOperationalPlan(gtu, path, startTime, segments, true);
267             return plan;
268         }
269         catch (GtuException exception)
270         {
271             throw new RuntimeException("Error during creation of lane change plan.", exception);
272         }
273     }
274 
275     /**
276      * Build an operational plan based on a simple operational plan and status info.
277      * @param gtu LaneBasedGtu; gtu
278      * @param startTime Time; start time for plan
279      * @param simplePlan SimpleOperationalPlan; simple operational plan
280      * @param laneChange LaneChange; lane change status
281      * @return operational plan
282      * @throws ParameterException if parameter is not defined
283      * @throws GtuException gtu exception
284      * @throws NetworkException network exception
285      * @throws OperationalPlanException operational plan exeption
286      */
287     public static LaneBasedOperationalPlan buildPlanFromSimplePlan(final LaneBasedGtu gtu, final Time startTime,
288             final SimpleOperationalPlan simplePlan, final LaneChange laneChange)
289             throws ParameterException, GtuException, NetworkException, OperationalPlanException
290     {
291         Acceleration acc = gtu.getVehicleModel().boundAcceleration(simplePlan.getAcceleration(), gtu);
292 
293         if (gtu.isInstantaneousLaneChange())
294         {
295             if (simplePlan.isLaneChange())
296             {
297                 gtu.changeLaneInstantaneously(simplePlan.getLaneChangeDirection());
298             }
299             try
300             {
301                 return LaneOperationalPlanBuilder.buildAccelerationPlan(gtu, startTime, gtu.getSpeed(), acc,
302                         simplePlan.getDuration(), false);
303             }
304             catch (OtsGeometryException exception)
305             {
306                 throw new OperationalPlanException(exception);
307             }
308         }
309 
310         // gradual lane change
311         try
312         {
313             if (!simplePlan.isLaneChange() && !laneChange.isChangingLane())
314             {
315                 return LaneOperationalPlanBuilder.buildAccelerationPlan(gtu, startTime, gtu.getSpeed(), acc,
316                         simplePlan.getDuration(), true);
317             }
318             if (gtu.getSpeed().si == 0.0 && acc.si <= 0.0)
319             {
320                 return LaneOperationalPlanBuilder.buildAccelerationPlan(gtu, startTime, gtu.getSpeed(), acc,
321                         simplePlan.getDuration(), false);
322             }
323             return LaneOperationalPlanBuilder.buildAccelerationLaneChangePlan(gtu, simplePlan.getLaneChangeDirection(),
324                     gtu.getLocation(), startTime, gtu.getSpeed(), acc, simplePlan.getDuration(), laneChange);
325         }
326         catch (OtsGeometryException exception)
327         {
328             throw new OperationalPlanException(exception);
329         }
330     }
331 
332     /**
333      * Schedules a lane change finalization after the given distance is covered. This distance is known as the plan is created,
334      * but at that point no time can be derived as the plan is required for that. Hence, this method can be scheduled at the
335      * same time (sequentially after creation of the plan) to then schedule the actual finalization by deriving time from
336      * distance with the plan.
337      * @param gtu LaneBasedGtu; gtu
338      * @param distance Length; distance
339      * @param laneChangeDirection LateralDirectionality; lane change direction
340      * @throws SimRuntimeException on bad time
341      */
342     public static void scheduleLaneChangeFinalization(final LaneBasedGtu gtu, final Length distance,
343             final LateralDirectionality laneChangeDirection) throws SimRuntimeException
344     {
345         Time time = gtu.getOperationalPlan().timeAtDistance(distance);
346         if (Double.isNaN(time.si))
347         {
348             // rounding...
349             time = gtu.getOperationalPlan().getEndTime();
350         }
351         SimEventInterface<Duration> event = gtu.getSimulator().scheduleEventAbsTime(time, (short) 6, gtu, "finalizeLaneChange",
352                 new Object[] {laneChangeDirection});
353         gtu.setFinalizeLaneChangeEvent(event);
354     }
355 
356 }