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