1 package org.opentrafficsim.road.gtu.lane.plan.operational;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Set;
8
9 import org.djunits.unit.AccelerationUnit;
10 import org.djunits.unit.DurationUnit;
11 import org.djunits.unit.LengthUnit;
12 import org.djunits.unit.SpeedUnit;
13 import org.djunits.value.ValueException;
14 import org.djunits.value.vdouble.scalar.Acceleration;
15 import org.djunits.value.vdouble.scalar.Duration;
16 import org.djunits.value.vdouble.scalar.Length;
17 import org.djunits.value.vdouble.scalar.Speed;
18 import org.djunits.value.vdouble.scalar.Time;
19 import org.opentrafficsim.core.geometry.OTSGeometryException;
20 import org.opentrafficsim.core.geometry.OTSLine3D;
21 import org.opentrafficsim.core.geometry.OTSPoint3D;
22 import org.opentrafficsim.core.gtu.GTUException;
23 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
24 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.SpeedSegment;
25 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
26 import org.opentrafficsim.core.math.Solver;
27 import org.opentrafficsim.core.network.LateralDirectionality;
28 import org.opentrafficsim.road.gtu.lane.AbstractLaneBasedGTU;
29 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
30 import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
31 import org.opentrafficsim.road.network.lane.DirectedLanePosition;
32 import org.opentrafficsim.road.network.lane.Lane;
33
34 import nl.tudelft.simulation.language.Throw;
35 import nl.tudelft.simulation.language.d3.DirectedPoint;
36
37 /**
38 * Builder for several often used operational plans, based on a list of lanes. E.g., decelerate to come to a full stop at the
39 * end of a shape; accelerate to reach a certain speed at the end of a curve; drive constant on a curve; decelerate or
40 * accelerate to reach a given end speed at the end of a curve, etc.<br>
41 * TODO driving with negative speeds (backward driving) is not yet supported.
42 * <p>
43 * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
44 * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
45 * </p>
46 * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
47 * initial version Nov 15, 2015 <br>
48 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
49 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
50 */
51 public final class LaneOperationalPlanBuilder
52 {
53 /** Maximum acceleration for unbounded accelerations: 1E12 m/s2. */
54 private static final Acceleration MAX_ACCELERATION = new Acceleration(1E12, AccelerationUnit.SI);
55
56 /** Maximum deceleration for unbounded accelerations: -1E12 m/s2. */
57 private static final Acceleration MAX_DECELERATION = new Acceleration(-1E12, AccelerationUnit.SI);
58
59 /** Private constructor. */
60 private 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, exactly at the end of the curve.
67 * The acceleration (and deceleration) are capped by maxAcceleration and maxDeceleration. Therefore, there is no guarantee
68 * that the end speed is actually reached by this plan.
69 * @param gtu the GTU for debugging purposes
70 * @param lanes a list of connected Lanes to do the driving on
71 * @param firstLanePosition position on the first lane with the reference point of the GTU
72 * @param distance distance to drive for reaching the end speed
73 * @param startTime the current time or a time in the future when the plan should start
74 * @param startSpeed the speed at the start of the path
75 * @param endSpeed the required end speed
76 * @param maxAcceleration the maximum acceleration that can be applied, provided as a POSITIVE number
77 * @param maxDeceleration the maximum deceleration that can be applied, provided as a NEGATIVE number
78 * @return the operational plan to accomplish the given end speed
79 * @throws OperationalPlanException when the plan cannot be generated, e.g. because of a path that is too short
80 * @throws OperationalPlanException when the length of the path and the calculated driven distance implied by the
81 * constructed segment list differ more than a given threshold
82 * @throws OTSGeometryException in case the lanes are not connected or firstLanePosition is larger than the length of the
83 * first lane
84 */
85 public static LaneBasedOperationalPlan buildGradualAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
86 final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
87 final Speed endSpeed, final Acceleration maxAcceleration, final Acceleration maxDeceleration)
88 throws OperationalPlanException, OTSGeometryException
89 {
90 OTSLine3D path = makePath(lanes, firstLanePosition, distance);
91 OperationalPlan.Segment segment;
92 if (startSpeed.eq(endSpeed))
93 {
94 segment = new SpeedSegment(distance.divideBy(startSpeed));
95 }
96 else
97 {
98 try
99 {
100 // t = 2x / (vt + v0); a = (vt - v0) / t
101 Duration duration = distance.multiplyBy(2.0).divideBy(endSpeed.plus(startSpeed));
102 Acceleration acceleration = endSpeed.minus(startSpeed).divideBy(duration);
103 if (acceleration.si < 0.0 && acceleration.lt(maxDeceleration))
104 {
105 acceleration = maxDeceleration;
106 duration = new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
107 DurationUnit.SI);
108 }
109 if (acceleration.si > 0.0 && acceleration.gt(maxAcceleration))
110 {
111 acceleration = maxAcceleration;
112 duration = new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
113 DurationUnit.SI);
114 }
115 segment = new OperationalPlan.AccelerationSegment(duration, acceleration);
116 }
117 catch (ValueException ve)
118 {
119 throw new OperationalPlanException(ve);
120 }
121 }
122 ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
123 segmentList.add(segment);
124 return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, lanes);
125 }
126
127 /**
128 * Build a plan with a path and a given start speed to try to reach a provided end speed, exactly at the end of the curve.
129 * The acceleration (and deceleration) are capped by maxAcceleration and maxDeceleration. Therefore, there is no guarantee
130 * that the end speed is actually reached by this plan.
131 * @param lanes a list of connected Lanes to do the driving on
132 * @param firstLanePosition position on the first lane with the reference point of the GTU
133 * @param distance distance to drive for reaching the end speed
134 * @return the driving path as a line
135 * @throws OperationalPlanException when the length of the lanes is less than the distance when we start at the
136 * firstLanePosition on the first lane, or when the lanes list contains no elements
137 * @throws OTSGeometryException in case the lanes are not connected or firstLanePosition is larger than the length of the
138 * first lane
139 */
140 public static OTSLine3D makePath(final List<Lane> lanes, final Length firstLanePosition, final Length distance)
141 throws OperationalPlanException, OTSGeometryException
142 {
143 if (lanes.size() == 0)
144 {
145 throw new OperationalPlanException("LaneOperationalPlanBuilder.makePath got a lanes list with size = 0");
146 }
147 OTSLine3D path = lanes.get(0).getCenterLine().extract(firstLanePosition, lanes.get(0).getLength());
148 for (int i = 1; i < lanes.size(); i++)
149 {
150 path = OTSLine3D.concatenate(Lane.MARGIN.si, path, lanes.get(i).getCenterLine());
151 }
152 return path.extract(0.0, distance.si);
153 }
154
155 /**
156 * Concatenate two paths, where the first may be {@code null}.
157 * @param path path, may be {@code null}
158 * @param centerLine center line of lane to add
159 * @return concatenated line
160 * @throws OTSGeometryException when lines are degenerate or too distant
161 */
162 public static OTSLine3D concatenateNull(final OTSLine3D path, final OTSLine3D centerLine) throws OTSGeometryException
163 {
164 if (path == null)
165 {
166 return centerLine;
167 }
168 return OTSLine3D.concatenate(Lane.MARGIN.si, path, centerLine);
169 }
170
171 /**
172 * Build a plan with a path and a given start speed to reach a provided end speed, exactly at the end of the curve.
173 * Acceleration and deceleration are virtually unbounded (1E12 m/s2) to reach the end speed (e.g., to come to a complete
174 * stop).
175 * @param gtu the GTU for debugging purposes
176 * @param lanes a list of connected Lanes to do the driving on
177 * @param firstLanePosition position on the first lane with the reference point of the GTU
178 * @param distance distance to drive for reaching the end speed
179 * @param startTime the current time or a time in the future when the plan should start
180 * @param startSpeed the speed at the start of the path
181 * @param endSpeed the required end speed
182 * @return the operational plan to accomplish the given end speed
183 * @throws OperationalPlanException when the length of the path and the calculated driven distance implied by the
184 * constructed segment list differ more than a given threshold
185 * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
186 * first lane
187 */
188 public static LaneBasedOperationalPlan buildGradualAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
189 final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
190 final Speed endSpeed) throws OperationalPlanException, OTSGeometryException
191 {
192 return buildGradualAccelerationPlan(gtu, lanes, firstLanePosition, distance, startTime, startSpeed, endSpeed,
193 MAX_ACCELERATION, MAX_DECELERATION);
194 }
195
196 /**
197 * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
198 * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
199 * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
200 * before completing the path, a truncated path that ends where the GTU stops is used instead.
201 * @param gtu the GTU for debugging purposes
202 * @param lanes a list of connected Lanes to do the driving on
203 * @param firstLanePosition position on the first lane with the reference point of the GTU
204 * @param distance distance to drive for reaching the end speed
205 * @param startTime the current time or a time in the future when the plan should start
206 * @param startSpeed the speed at the start of the path
207 * @param endSpeed the required end speed
208 * @param acceleration the acceleration to use if endSpeed > startSpeed, provided as a POSITIVE number
209 * @param deceleration the deceleration to use if endSpeed < startSpeed, provided as a NEGATIVE number
210 * @return the operational plan to accomplish the given end speed
211 * @throws OperationalPlanException when the construction of the operational path fails
212 * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
213 * first lane
214 */
215 public static LaneBasedOperationalPlan buildMaximumAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
216 final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
217 final Speed endSpeed, final Acceleration acceleration, final Acceleration deceleration)
218 throws OperationalPlanException, OTSGeometryException
219 {
220 OTSLine3D path = makePath(lanes, firstLanePosition, distance);
221 ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
222 if (startSpeed.eq(endSpeed))
223 {
224 segmentList.add(new OperationalPlan.SpeedSegment(distance.divideBy(startSpeed)));
225 }
226 else
227 {
228 try
229 {
230 if (endSpeed.gt(startSpeed))
231 {
232 Duration t = endSpeed.minus(startSpeed).divideBy(acceleration);
233 Length x = startSpeed.multiplyBy(t).plus(acceleration.multiplyBy(0.5).multiplyBy(t).multiplyBy(t));
234 if (x.ge(distance))
235 {
236 // we cannot reach the end speed in the given distance with the given acceleration
237 Duration duration =
238 new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
239 DurationUnit.SI);
240 segmentList.add(new OperationalPlan.AccelerationSegment(duration, acceleration));
241 }
242 else
243 {
244 // we reach the (higher) end speed before the end of the segment. Make two segments.
245 segmentList.add(new OperationalPlan.AccelerationSegment(t, acceleration));
246 Duration duration = distance.minus(x).divideBy(endSpeed);
247 segmentList.add(new OperationalPlan.SpeedSegment(duration));
248 }
249 }
250 else
251 {
252 Duration t = endSpeed.minus(startSpeed).divideBy(deceleration);
253 Length x = startSpeed.multiplyBy(t).plus(deceleration.multiplyBy(0.5).multiplyBy(t).multiplyBy(t));
254 if (x.ge(distance))
255 {
256 // we cannot reach the end speed in the given distance with the given deceleration
257 Duration duration =
258 new Duration(Solver.firstSolutionAfter(0, deceleration.si / 2, startSpeed.si, -distance.si),
259 DurationUnit.SI);
260 segmentList.add(new OperationalPlan.AccelerationSegment(duration, deceleration));
261 }
262 else
263 {
264 if (endSpeed.si == 0.0)
265 {
266 // if endSpeed == 0, we cannot reach the end of the path. Therefore, build a partial path.
267 OTSLine3D partialPath = path.truncate(x.si);
268 segmentList.add(new OperationalPlan.AccelerationSegment(t, deceleration));
269 return new LaneBasedOperationalPlan(gtu, partialPath, startTime, startSpeed, segmentList, lanes);
270 }
271 // we reach the (lower) end speed, larger than zero, before the end of the segment. Make two segments.
272 segmentList.add(new OperationalPlan.AccelerationSegment(t, deceleration));
273 Duration duration = distance.minus(x).divideBy(endSpeed);
274 segmentList.add(new OperationalPlan.SpeedSegment(duration));
275 }
276 }
277 }
278 catch (ValueException ve)
279 {
280 throw new OperationalPlanException(ve);
281 }
282
283 }
284 return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, lanes);
285 }
286
287 /**
288 * Minimum distance of an operational plan path; anything shorter will be truncated to 0. <br>
289 * If objects related to e.g. molecular movements are simulated using this code, a setter for this parameter will be needed.
290 */
291 private static final Length MINIMUM_CREDIBLE_PATH_LENGTH = new Length(0.001, LengthUnit.METER);
292
293 /**
294 * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
295 * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
296 * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
297 * before completing the path, a truncated path that ends where the GTU stops is used instead.
298 * @param gtu the GTU for debugging purposes
299 * @param lanes a list of connected Lanes to do the driving on
300 * @param firstLanePosition position on the first lane with the reference point of the GTU
301 * @param startTime the current time or a time in the future when the plan should start
302 * @param startSpeed the speed at the start of the path
303 * @param acceleration the acceleration to use
304 * @param timeStep time step for the plan
305 * @return the operational plan to accomplish the given end speed
306 * @throws OperationalPlanException when the construction of the operational path fails
307 * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
308 * first lane
309 */
310 public static LaneBasedOperationalPlan buildAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
311 final Length firstLanePosition, final Time startTime, final Speed startSpeed, final Acceleration acceleration,
312 final Duration timeStep) throws OperationalPlanException, OTSGeometryException
313 {
314
315 if (startSpeed.si <= OperationalPlan.DRIFTING_SPEED_SI && acceleration.le(Acceleration.ZERO))
316 {
317 return new LaneBasedOperationalPlan(gtu, lanes.get(0).getCenterLine().getLocation(firstLanePosition), startTime,
318 timeStep, lanes.get(0));
319 }
320 Length distance;
321 ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
322 if (startSpeed.plus(acceleration.multiplyBy(timeStep)).lt0())
323 {
324 // will reach stand-still within time step
325 Duration brakingTime = startSpeed.divideBy(acceleration.neg());
326 segmentList.add(new OperationalPlan.AccelerationSegment(brakingTime, acceleration));
327 segmentList.add(new OperationalPlan.SpeedSegment(timeStep.minus(brakingTime)));
328 distance = new Length(startSpeed.si * brakingTime.si + .5 * acceleration.si * brakingTime.si * brakingTime.si,
329 LengthUnit.SI);
330 }
331 else
332 {
333 segmentList.add(new OperationalPlan.AccelerationSegment(timeStep, acceleration));
334 distance =
335 new Length(startSpeed.si * timeStep.si + .5 * acceleration.si * timeStep.si * timeStep.si, LengthUnit.SI);
336 }
337 if (distance.le(MINIMUM_CREDIBLE_PATH_LENGTH))
338 {
339 System.err.println("Path too short; replacing operational plan with distance " + distance + " by a wait plan");
340 return new LaneBasedOperationalPlan(gtu, gtu.getLocation(), startTime, timeStep, lanes.get(0));
341 }
342 OTSLine3D path;
343 try
344 {
345 path = makePath(lanes, firstLanePosition, distance);
346 }
347 catch (Exception e)
348 {
349 throw new Error("Path for acceleration plan could not be made.", e);
350 }
351 return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, lanes);
352 }
353
354 /**
355 * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
356 * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
357 * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
358 * before completing the path, a truncated path that ends where the GTU stops is used instead.
359 * @param gtu the GTU for debugging purposes
360 * @param fromLanes lanes where the GTU changes from
361 * @param laneChangeDirectionality direction of lane change
362 * @param startPosition current position
363 * @param startTime the current time or a time in the future when the plan should start
364 * @param startSpeed the speed at the start of the path
365 * @param acceleration the acceleration to use
366 * @param timeStep time step for the plan
367 * @param laneChange lane change status
368 * @return the operational plan to accomplish the given end speed
369 * @throws OperationalPlanException when the construction of the operational path fails
370 * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
371 * first lane
372 */
373 @SuppressWarnings("checkstyle:parameternumber")
374 public static LaneBasedOperationalPlan buildAccelerationLaneChangePlan(final LaneBasedGTU gtu, final List<Lane> fromLanes,
375 final LateralDirectionality laneChangeDirectionality, final DirectedPoint startPosition, final Time startTime,
376 final Speed startSpeed, final Acceleration acceleration, final Duration timeStep, final LaneChange laneChange)
377 throws OperationalPlanException, OTSGeometryException
378 {
379 // on first call during lane change, use laneChangeDirectionality as laneChange.getDirection() is probably NONE
380 LateralDirectionality direction = laneChange.isChangingLane() ? laneChange.getDirection() : laneChangeDirectionality;
381 Length fromLaneDistance =
382 new Length(startSpeed.si * timeStep.si + .5 * acceleration.si * timeStep.si * timeStep.si, LengthUnit.SI);
383 // TODO also for other driving directions, additional arguments in projectFractional?
384 double firstFractionalPosition = fromLanes.get(0).getCenterLine().projectFractional(
385 fromLanes.get(0).getParentLink().getStartNode().getDirection(),
386 fromLanes.get(0).getParentLink().getEndNode().getDirection(), startPosition.x, startPosition.y);
387 Length fromLaneFirstPosition = fromLanes.get(0).position(firstFractionalPosition);
388 Length cumulDistance = fromLanes.get(0).getLength().minus(fromLaneFirstPosition);
389 int lastLaneIndex = 0;
390 while (cumulDistance.lt(fromLaneDistance))
391 {
392 lastLaneIndex++;
393 cumulDistance = cumulDistance.plus(fromLanes.get(lastLaneIndex).getLength());
394 }
395 double lastFractionalPosition = fromLanes.get(lastLaneIndex).getLength().minus(cumulDistance.minus(fromLaneDistance)).si
396 / fromLanes.get(lastLaneIndex).getLength().si;
397
398 List<Lane> toLanes = new ArrayList<>();
399 for (Lane lane : fromLanes)
400 {
401 if (!lane.accessibleAdjacentLanes(direction, gtu.getGTUType()).isEmpty())
402 {
403 toLanes.add(lane.accessibleAdjacentLanes(direction, gtu.getGTUType()).iterator().next());
404 }
405 else
406 {
407 new Exception().printStackTrace();
408 System.exit(-1);
409 }
410 }
411
412 Length toLaneFirstPosition = toLanes.get(0).position(firstFractionalPosition);
413 Length fromLaneLastPosition = fromLanes.get(lastLaneIndex).position(lastFractionalPosition);
414 Length toLaneLastPosition = toLanes.get(lastLaneIndex).position(lastFractionalPosition);
415
416 DirectedPoint fromFirst = fromLanes.get(0).getCenterLine().getLocation(fromLaneFirstPosition);
417 DirectedPoint toFirst = toLanes.get(0).getCenterLine().getLocation(toLaneFirstPosition);
418 DirectedPoint fromLast = fromLanes.get(lastLaneIndex).getCenterLine().getLocation(fromLaneLastPosition);
419 DirectedPoint toLast = toLanes.get(lastLaneIndex).getCenterLine().getLocation(toLaneLastPosition);
420
421 double lastFraction = laneChange.updateAndGetFraction(timeStep, direction, gtu);
422 OTSPoint3D lastPoint = new OTSPoint3D(fromLast.x * (1 - lastFraction) + toLast.x * lastFraction,
423 fromLast.y * (1 - lastFraction) + toLast.y * lastFraction,
424 fromLast.z * (1 - lastFraction) + toLast.z * lastFraction);
425 OTSPoint3D firstPoint = new OTSPoint3D(startPosition);
426 OTSLine3D path = new OTSLine3D(firstPoint, lastPoint);
427
428 double t = timeStep.si;
429 // why was this used?
430 // Acceleration a = new Acceleration((2.0 * (path.getLength().si - startSpeed.si * t)) / (t * t), AccelerationUnit.SI);
431 Speed endSpeed = startSpeed.plus(acceleration.multiplyBy(timeStep));
432 ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
433 if (endSpeed.lt0())
434 {
435 Duration brakingTime = startSpeed.divideBy(acceleration.neg());
436 if (brakingTime.si > 0.0)
437 {
438 segmentList.add(new OperationalPlan.AccelerationSegment(brakingTime, acceleration));
439 }
440 segmentList.add(new OperationalPlan.SpeedSegment(timeStep.minus(brakingTime)));
441 }
442 else
443 {
444 segmentList.add(new OperationalPlan.AccelerationSegment(timeStep, acceleration));
445 }
446 return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, fromLanes, toLanes, lastLaneIndex,
447 lastFractionalPosition);
448 }
449
450 /**
451 * Lane change status across operational plans.
452 * <p>
453 * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
454 * <br>
455 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
456 * <p>
457 * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 26, 2016 <br>
458 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
459 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
460 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
461 */
462 public static class LaneChange implements Serializable
463 {
464
465 /** */
466 private static final long serialVersionUID = 20160811L;
467
468 /** Lane change progress. */
469 private Duration laneChangeProgress;
470
471 /** Total lane change duration. */
472 private Duration laneChangeDuration;
473
474 /** Whether the GTU is changing lane. */
475 private LateralDirectionality laneChangeDirectionality = null;
476
477 /**
478 * Return whether the GTU is changing lane.
479 * @return whether the GTU is changing lane
480 */
481 public final boolean isChangingLane()
482 {
483 return this.laneChangeDirectionality != null;
484 }
485
486 /**
487 * Return whether the GTU is changing left.
488 * @return whether the GTU is changing left
489 */
490 public final boolean isChangingLeft()
491 {
492 return LateralDirectionality.LEFT.equals(this.laneChangeDirectionality);
493 }
494
495 /**
496 * Return whether the GTU is changing right.
497 * @return whether the GTU is changing right
498 */
499 public final boolean isChangingRight()
500 {
501 return LateralDirectionality.RIGHT.equals(this.laneChangeDirectionality);
502 }
503
504 /**
505 * Return lateral lane change direction.
506 * @return LateralDirectionality; lateral lane change direction
507 */
508 public final LateralDirectionality getDirection()
509 {
510 return this.laneChangeDirectionality;
511 }
512
513 /**
514 * @param laneChangeDirectionality set laneChangeDirectionality.
515 */
516 public final void setLaneChangeDirectionality(final LateralDirectionality laneChangeDirectionality)
517 {
518 this.laneChangeDirectionality = laneChangeDirectionality;
519 }
520
521 /**
522 * Second lane of lane change relative to the reference lane. Note that the reference lane may either be the source or
523 * the target lane. Thus, the second lane during a lane change may either be the left or right lane, regardless of the
524 * lane change direction.
525 * @param gtu LaneBasedGTU; gtu
526 * @return target lane of lane change
527 * @throws OperationalPlanException If no lane change is being performed.
528 */
529 public final RelativeLane getSecondLane(final LaneBasedGTU gtu) throws OperationalPlanException
530 {
531 Throw.when(!isChangingLane(), OperationalPlanException.class,
532 "Target lane is requested, but no lane change is being performed.");
533 Map<Lane, Length> map;
534 DirectedLanePosition dlp;
535 try
536 {
537 map = gtu.positions(gtu.getReference());
538 dlp = gtu.getReferencePosition();
539 }
540 catch (GTUException exception)
541 {
542 throw new OperationalPlanException("Second lane of lane change could not be determined.", exception);
543 }
544 Set<Lane> accessibleLanes = dlp.getLane().accessibleAdjacentLanes(this.laneChangeDirectionality, gtu.getGTUType());
545 if (!accessibleLanes.isEmpty() && map.containsKey(accessibleLanes.iterator().next()))
546 {
547 return isChangingLeft() ? RelativeLane.LEFT : RelativeLane.RIGHT;
548 }
549 return isChangingLeft() ? RelativeLane.RIGHT : RelativeLane.LEFT;
550 }
551
552 /**
553 * Sets the duration for a lane change. May be set during a lane change. Whenever the lane change progress exceeds the
554 * lane change duration, the lane change is finalized in the next time step.
555 * @param laneChangeDuration total lane change duration
556 */
557 public final void setLaneChangeDuration(final Duration laneChangeDuration)
558 {
559 this.laneChangeDuration = laneChangeDuration;
560 }
561
562 /**
563 * Update the lane change and return the lateral fraction for the end of the coming time step. This method is for use by
564 * the operational plan builder. It adds the time step duration to the lane change progress. Lane changes are
565 * automatically initiated and finalized.
566 * @param timeStep coming time step duration
567 * @param laneChangeDirection direction of lane change
568 * @param gtu GTU
569 * @return lateral fraction for the end of the coming time step
570 */
571 // TODO remove GTU argument (only needed for instantaneous lane change hack)
572 final double updateAndGetFraction(final Duration timeStep, final LateralDirectionality laneChangeDirection,
573 final LaneBasedGTU gtu)
574 {
575 if (!isChangingLane())
576 {
577 // initiate lane change
578 this.laneChangeProgress = Duration.ZERO;
579 this.laneChangeDirectionality = laneChangeDirection;
580 try
581 {
582 ((AbstractLaneBasedGTU) gtu).initLaneChange(laneChangeDirection);
583 }
584 catch (GTUException exception)
585 {
586 throw new RuntimeException("Error during lane change initialization.", exception);
587 }
588 }
589 // add current time step
590 this.laneChangeProgress = this.laneChangeProgress.plus(timeStep);
591 // get lateral fraction at end of current time step
592 double fraction = this.laneChangeProgress.divideBy(this.laneChangeDuration).si;
593 if (fraction >= 1.0)
594 {
595 // TODO this elsewhere based on path
596 try
597 {
598 ((AbstractLaneBasedGTU) gtu).finalizeLaneChange(laneChangeDirection,
599 gtu.getSimulator().getSimulatorTime().getTime().plus(timeStep));
600 }
601 catch (GTUException exception)
602 {
603 throw new RuntimeException("Error during lane change finalization.", exception);
604 }
605 // limit by 1 and finalize lane change
606 this.laneChangeDirectionality = null;
607 return 1.0;
608 }
609 return fraction;
610 }
611
612 /** {@inheritDoc} */
613 public final String toString()
614 {
615 return "LaneChange [" + this.laneChangeProgress + " of " + this.laneChangeDuration + " to "
616 + this.laneChangeDirectionality + "]";
617 }
618
619 }
620
621 /**
622 * Build a plan with a path and a given start speed to try to come to a stop with a given deceleration. If the GTU can stop
623 * before completing the given path, a truncated path that ends where the GTU stops is used instead. There is no guarantee
624 * that the OperationalPlan will lead to a complete stop.
625 * @param gtu the GTU for debugging purposes
626 * @param lanes a list of connected Lanes to do the driving on
627 * @param firstLanePosition position on the first lane with the reference point of the GTU
628 * @param distance distance to drive for reaching the end speed
629 * @param startTime the current time or a time in the future when the plan should start
630 * @param startSpeed the speed at the start of the path
631 * @param deceleration the deceleration to use if endSpeed < startSpeed, provided as a NEGATIVE number
632 * @return the operational plan to accomplish the given end speed
633 * @throws OperationalPlanException when construction of the operational path fails
634 * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
635 * first lane
636 */
637 public static LaneBasedOperationalPlan buildStopPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
638 final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
639 final Acceleration deceleration) throws OperationalPlanException, OTSGeometryException
640 {
641 return buildMaximumAccelerationPlan(gtu, lanes, firstLanePosition, distance, startTime, startSpeed,
642 new Speed(0.0, SpeedUnit.SI), new Acceleration(1.0, AccelerationUnit.SI), deceleration);
643 }
644
645 /*-
646 public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Time startTime,
647 final Acceleration startAcceleration, final Speed maxSpeed, final Duration duration,
648 final List<LaneDirection> fromLanes, List<LaneDirection> toLanes, final CurvatureType curvatureType,
649 final double laneChangeProgress, final DirectedPoint endPoint) throws OperationalPlanException
650 {
651 return buildSpatialPlan(gtu, startTime, gtu.getLocation(), gtu.getSpeed(), startAcceleration, maxSpeed, duration,
652 fromLanes, toLanes, curvatureType, laneChangeProgress, endPoint);
653 }
654
655 public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Time startTime,
656 final DirectedPoint startPoint, final Speed startSpeed, final Acceleration startAcceleration, final Speed maxSpeed,
657 final Duration duration, final List<LaneDirection> fromLanes, List<LaneDirection> toLanes,
658 final CurvatureType curvatureType, final double laneChangeProgress, final DirectedPoint endPoint)
659 throws OperationalPlanException
660 {
661
662 // check
663 checkLaneDirections(fromLanes, toLanes);
664
665 // get start fractional position on link
666 final CrossSectionLink startLink = fromLanes.get(0).getLane().getParentLink();
667 Direction start;
668 Direction end;
669 if (fromLanes.get(0).getDirection() == GTUDirectionality.DIR_PLUS)
670 {
671 start = startLink.getStartNode().getDirection();
672 end = startLink.getEndNode().getDirection();
673 }
674 else
675 {
676 start = startLink.getEndNode().getDirection();
677 end = startLink.getStartNode().getDirection();
678 }
679 double fStart = startLink.getDesignLine().projectFractional(start, end, startPoint.x, startPoint.y);
680
681 // get end fractional position on link, and end link
682 double fEnd = 0;
683 CrossSectionLink endLink = null;
684 for (int i = 0; i < toLanes.size(); i++)
685 {
686 CrossSectionLink l = toLanes.get(i).getLane().getParentLink();
687 if (toLanes.get(i).getDirection() == GTUDirectionality.DIR_PLUS)
688 {
689 start = l.getStartNode().getDirection();
690 end = l.getEndNode().getDirection();
691 }
692 else
693 {
694 start = l.getEndNode().getDirection();
695 end = l.getStartNode().getDirection();
696 }
697 fEnd = l.getDesignLine().projectFractional(start, end, endPoint.x, endPoint.y);
698 if (fEnd > 0 && fEnd < 1)
699 {
700 endLink = l;
701 break;
702 }
703 }
704 Throw.when(endLink == null, OperationalPlanException.class, "End point cannot be projected to to-lanes.");
705
706 // build from-line and to-line
707 OTSLine3D from = null;
708 OTSLine3D to = null;
709 for (int i = 0; i < toLanes.size(); i++)
710 {
711 CrossSectionLink l = toLanes.get(i).getLane().getParentLink();
712 try
713 {
714 if (l == startLink)
715 {
716 from = fromLanes.get(i).getLane().getCenterLine().extractFractional(fStart, 1);
717 to = toLanes.get(i).getLane().getCenterLine().extractFractional(fStart, 1);
718 }
719 else if (l == endLink)
720 {
721 from =
722 OTSLine3D.concatenate(from, fromLanes.get(i).getLane().getCenterLine().extractFractional(0, fEnd));
723 to = OTSLine3D.concatenate(to, toLanes.get(i).getLane().getCenterLine().extractFractional(0, fEnd));
724 break;
725 }
726 from = OTSLine3D.concatenate(from, fromLanes.get(i).getLane().getCenterLine());
727 to = OTSLine3D.concatenate(to, toLanes.get(i).getLane().getCenterLine());
728 }
729 catch (OTSGeometryException exception)
730 {
731 throw new RuntimeException("Bug in buildSpatialPlan method.");
732 }
733 }
734
735 // interpolate path
736 List<OTSPoint3D> line = new ArrayList<>();
737 line.add(new OTSPoint3D(startPoint.x, startPoint.y, startPoint.z));
738 if (curvatureType.equals(CurvatureType.LINEAR))
739 {
740 int n = (int) Math.ceil(32 * (1.0 - laneChangeProgress));
741 for (int i = 1; i < n; i++)
742 {
743 double fraction = 1.0 * i / n;
744 double f0 = laneChangeProgress + (1.0 - laneChangeProgress) * fraction;
745 double f1 = 1.0 - f0;
746 DirectedPoint p1;
747 DirectedPoint p2;
748 try
749 {
750 p1 = from.getLocationFraction(fraction);
751 p2 = to.getLocationFraction(fraction);
752 }
753 catch (OTSGeometryException exception)
754 {
755 throw new RuntimeException("Bug in buildSpatialPlan method.");
756 }
757 line.add(new OTSPoint3D(p1.x * f1 + p2.x * f0, p1.y * f1 + p2.y * f0, p1.z * f1 + p2.z * f0));
758 }
759 }
760 OTSLine3D path;
761 try
762 {
763 path = new OTSLine3D(line);
764 }
765 catch (OTSGeometryException exception)
766 {
767 throw new RuntimeException("Bug in buildSpatialPlan method.");
768 }
769
770 // acceleration segments
771 List<Segment> segmentList = new ArrayList<>();
772 Acceleration b = startAcceleration.neg();
773 if (startSpeed.lt(b.multiplyBy(duration)))
774 {
775 // will reach zero speed within duration
776 Duration d = startSpeed.divideBy(b);
777 segmentList.add(new AccelerationSegment(d, startAcceleration)); // decelerate to zero
778 segmentList.add(new SpeedSegment(duration.minus(d))); // stay at zero for the remainder of duration
779 }
780 else
781 {
782 segmentList.add(new AccelerationSegment(duration, startAcceleration));
783 }
784
785 return new OperationalPlan(gtu, path, startTime, startSpeed, segmentList);
786 }
787
788 public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Acceleration startAcceleration,
789 final Speed maxSpeed, final List<LaneDirection> fromLanes, List<LaneDirection> toLanes,
790 final CurvatureType curvatureType, final Duration duration) throws OperationalPlanException
791 {
792 return buildSpatialPlan(gtu, gtu.getLocation(), gtu.getSpeed(), startAcceleration, maxSpeed, fromLanes, toLanes,
793 curvatureType, duration);
794 }
795
796 public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final DirectedPoint startPoint,
797 final Speed startSpeed, final Acceleration startAcceleration, final Speed maxSpeed,
798 final List<LaneDirection> fromLanes, List<LaneDirection> toLanes, final CurvatureType curvatureType,
799 final Duration duration) throws OperationalPlanException
800 {
801 checkLaneDirections(fromLanes, toLanes);
802
803 return null;
804 }
805
806 private final static void checkLaneDirections(final List<LaneDirection> fromLanes, List<LaneDirection> toLanes)
807 throws OperationalPlanException
808 {
809 Throw.when(fromLanes == null || toLanes == null, OperationalPlanException.class, "Lane lists may not be null.");
810 Throw.when(fromLanes.isEmpty(), OperationalPlanException.class, "Lane lists may not be empty.");
811 Throw.when(fromLanes.size() != toLanes.size(), OperationalPlanException.class,
812 "Set of from lanes has different length than set of to lanes.");
813 for (int i = 0; i < fromLanes.size(); i++)
814 {
815 Throw.when(!fromLanes.get(i).getLane().getParentLink().equals(toLanes.get(i).getLane().getParentLink()),
816 OperationalPlanException.class,
817 "A lane in the from-lanes list is not on the same link as the lane at equal index in the to-lanes list.");
818 }
819 }
820
821 /**
822 * Defines curvature.
823 * <p>
824 * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
825 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
826 * <p>
827 * @version $Revision$, $LastChangedDate$, by $Author$, initial version May 27, 2016 <br>
828 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
829 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
830 */
831 /*-
832 public enum CurvatureType
833 {
834 /** Linear lateral movement. */
835 /*-
836 LINEAR
837 {
838 public double[] getFractions(final double strartFraction)
839 {
840 return new double[1];
841 }
842 },
843
844 /** */
845 /*-
846 SCURVE
847 {
848 public double[] getFractions(final double strartFraction)
849 {
850 return new double[1];
851 }
852 };
853
854 public abstract double[] getFractions(double startFraction);
855 }
856 */
857
858 }