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