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