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