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