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.value.vdouble.scalar.Duration;
10 import org.djunits.value.vdouble.scalar.Length;
11 import org.djunits.value.vdouble.scalar.Speed;
12 import org.opentrafficsim.core.geometry.Bezier;
13 import org.opentrafficsim.core.geometry.OTSGeometryException;
14 import org.opentrafficsim.core.geometry.OTSLine3D;
15 import org.opentrafficsim.core.geometry.OTSPoint3D;
16 import org.opentrafficsim.core.gtu.GTUException;
17 import org.opentrafficsim.core.gtu.Try;
18 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
19 import org.opentrafficsim.core.network.LateralDirectionality;
20 import org.opentrafficsim.road.gtu.lane.AbstractLaneBasedGTU;
21 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
22 import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
23 import org.opentrafficsim.road.network.lane.DirectedLanePosition;
24 import org.opentrafficsim.road.network.lane.Lane;
25 import org.opentrafficsim.road.network.lane.LaneDirection;
26
27 import nl.tudelft.simulation.dsol.SimRuntimeException;
28 import nl.tudelft.simulation.language.Throw;
29 import nl.tudelft.simulation.language.d3.DirectedPoint;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 public class LaneChange implements Serializable
45 {
46
47
48 private static final long serialVersionUID = 20160811L;
49
50
51 private Duration desiredLaneChangeDuration;
52
53
54 private double fraction;
55
56
57 private Length boundary;
58
59
60 private LaneChangePath laneChangePath = LaneChangePath.BEZIER;
61
62
63 private LateralDirectionality laneChangeDirectionality = null;
64
65
66 private static final LaneOperationalPlanBuilder BUILDER = new LaneOperationalPlanBuilder();
67
68
69 public static double MIN_LC_LENGTH_FACTOR = 2.0;
70
71
72
73
74
75 public void setDesiredLaneChangeDuration(final Duration duration)
76 {
77 this.desiredLaneChangeDuration = duration;
78 }
79
80
81
82
83
84
85
86 public void setBoundary(final Length boundary)
87 {
88 this.boundary = boundary;
89 }
90
91
92
93
94
95 public double getFraction()
96 {
97 return this.fraction;
98 }
99
100
101
102
103
104 public void setLaneChangePath(final LaneChangePath laneChangePath)
105 {
106 this.laneChangePath = laneChangePath;
107 }
108
109
110
111
112
113 public final boolean isChangingLane()
114 {
115 return this.laneChangeDirectionality != null;
116 }
117
118
119
120
121
122 public final boolean isChangingLeft()
123 {
124 return LateralDirectionality.LEFT.equals(this.laneChangeDirectionality);
125 }
126
127
128
129
130
131 public final boolean isChangingRight()
132 {
133 return LateralDirectionality.RIGHT.equals(this.laneChangeDirectionality);
134 }
135
136
137
138
139
140 public final LateralDirectionality getDirection()
141 {
142 return this.laneChangeDirectionality;
143 }
144
145
146
147
148
149
150
151
152
153 public final RelativeLane getSecondLane(final LaneBasedGTU gtu) throws OperationalPlanException
154 {
155 Throw.when(!isChangingLane(), OperationalPlanException.class,
156 "Target lane is requested, but no lane change is being performed.");
157 Map<Lane, Length> map;
158 DirectedLanePosition dlp;
159 try
160 {
161 map = gtu.positions(gtu.getReference());
162 dlp = gtu.getReferencePosition();
163 }
164 catch (GTUException exception)
165 {
166 throw new OperationalPlanException("Second lane of lane change could not be determined.", exception);
167 }
168 Set<Lane> accessibleLanes = dlp.getLane().accessibleAdjacentLanesPhysical(this.laneChangeDirectionality,
169 gtu.getGTUType(), dlp.getGtuDirection());
170 if (!accessibleLanes.isEmpty() && map.containsKey(accessibleLanes.iterator().next()))
171 {
172 return isChangingLeft() ? RelativeLane.LEFT : RelativeLane.RIGHT;
173 }
174 return isChangingLeft() ? RelativeLane.RIGHT : RelativeLane.LEFT;
175 }
176
177
178
179
180
181
182
183
184
185
186
187
188 public final OTSLine3D getPath(final Duration timeStep, final LaneBasedGTU gtu, final DirectedLanePosition from,
189 final DirectedPoint startPosition, final Length planDistance, final LateralDirectionality laneChangeDirection)
190 throws OTSGeometryException
191 {
192
193
194 if (!isChangingLane())
195 {
196 this.laneChangeDirectionality = laneChangeDirection;
197 Try.execute(() -> ((AbstractLaneBasedGTU) gtu).initLaneChange(laneChangeDirection),
198 "Error during lane change initialization.");
199 }
200
201
202
203
204
205
206
207
208
209
210 Speed meanSpeed = planDistance.divideBy(timeStep);
211 double minDistance = gtu.getLength().si * MIN_LC_LENGTH_FACTOR;
212 double minDuration = minDistance / meanSpeed.si;
213 double laneChangeDuration = Math.max(this.desiredLaneChangeDuration.si, minDuration);
214 if (this.boundary != null)
215 {
216 double maxDuration = this.boundary.si / meanSpeed.si;
217 laneChangeDuration = Math.min(laneChangeDuration, maxDuration);
218 }
219
220 double totalLength = laneChangeDuration * meanSpeed.si;
221 double fromDist = (1.0 - this.fraction) * totalLength;
222 Throw.when(fromDist < 0.0, RuntimeException.class, "Lane change results in negative distance along from lanes.");
223
224
225 LaneDirection fromLane = from.getLaneDirection();
226 List<LaneDirection> fromLanes = new ArrayList<>();
227 List<LaneDirection> toLanes = new ArrayList<>();
228 fromLanes.add(fromLane);
229 toLanes.add(fromLane.getAdjacentLaneDirection(this.laneChangeDirectionality, gtu));
230 double endPosFrom = from.getPosition().si + fromDist;
231 while (endPosFrom + gtu.getFront().getDx().si > fromLane.getLane().getLength().si)
232 {
233 LaneDirection nextFromLane = fromLane.getNextLaneDirection(gtu);
234 if (nextFromLane == null)
235 {
236
237 double endFromPosLimit = fromLane.getLane().getLength().si - gtu.getFront().getDx().si;
238 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
239 laneChangeDuration *= f;
240 endPosFrom = endFromPosLimit;
241 break;
242 }
243 endPosFrom -= fromLane.getLane().getLength().si;
244 LaneDirection nextToLane = nextFromLane.getAdjacentLaneDirection(this.laneChangeDirectionality, gtu);
245 if (nextToLane == null)
246 {
247
248 double endFromPosLimit = fromLane.getLane().getLength().si - gtu.getFront().getDx().si;
249 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
250 laneChangeDuration *= f;
251 endPosFrom = endFromPosLimit;
252 break;
253 }
254 fromLane = nextFromLane;
255 fromLanes.add(fromLane);
256 toLanes.add(nextToLane);
257 }
258
259 while (endPosFrom < 0.0)
260 {
261 fromLanes.remove(fromLanes.size() - 1);
262 toLanes.remove(toLanes.size() - 1);
263 fromLane = fromLanes.get(fromLanes.size() - 1);
264 endPosFrom += fromLane.getLane().getLength().si;
265 }
266
267 double endFraction = fromLane.fractionAtCoveredDistance(Length.createSI(endPosFrom));
268
269
270 OTSLine3D path = this.laneChangePath.getPath(timeStep, planDistance, meanSpeed, from, startPosition,
271 laneChangeDirection, fromLanes, toLanes, endFraction, Duration.createSI(laneChangeDuration), this.fraction);
272
273
274 this.fraction += timeStep.si / laneChangeDuration;
275
276
277 double requiredLength = planDistance.si - path.getLength().si;
278 if (requiredLength > 0.0 || this.fraction > 0.999)
279 {
280 try
281 {
282
283 gtu.getSimulator().scheduleEventNow(gtu, BUILDER, "scheduleLaneChangeFinalization", new Object[] {
284 (AbstractLaneBasedGTU) gtu, Length.min(planDistance, path.getLength()), laneChangeDirection });
285 }
286 catch (SimRuntimeException exception)
287 {
288 throw new RuntimeException("Error during lane change finalization.", exception);
289 }
290
291 if (requiredLength > 0.0)
292 {
293 LaneDirection toLane = toLanes.get(toLanes.size() - 1);
294 int n = path.size();
295
296 if (0.0 < endFraction && endFraction < 1.0)
297 {
298 OTSLine3D remainder = toLane.getDirection().isPlus()
299 ? toLane.getLane().getCenterLine().extractFractional(endFraction, 1.0)
300 : toLane.getLane().getCenterLine().extractFractional(0.0, endFraction).reverse();
301 path = OTSLine3D.concatenate(0.001, path, remainder);
302 requiredLength = planDistance.si - path.getLength().si;
303 }
304
305 while (requiredLength > 0.0)
306 {
307 toLane = toLane.getNextLaneDirection(gtu);
308 OTSLine3D remainder = toLane.getDirection().isPlus() ? toLane.getLane().getCenterLine()
309 : toLane.getLane().getCenterLine().reverse();
310 path = OTSLine3D.concatenate(Lane.MARGIN.si, path, remainder);
311 requiredLength = planDistance.si - path.getLength().si + Lane.MARGIN.si;
312 }
313
314 if (this.fraction > 0.999)
315 {
316 OTSPoint3D[] points = new OTSPoint3D[path.size() - 1];
317 System.arraycopy(path.getPoints(), 0, points, 0, n - 1);
318 System.arraycopy(path.getPoints(), n, points, n - 1, path.size() - n);
319 path = new OTSLine3D(points);
320 }
321 }
322
323 this.laneChangeDirectionality = null;
324 this.boundary = null;
325 this.fraction = 0.0;
326 }
327 return path;
328 }
329
330
331 @Override
332 public String toString()
333 {
334 return "LaneChange [fraction=" + this.fraction + ", laneChangeDirectionality=" + this.laneChangeDirectionality + "]";
335 }
336
337
338
339
340
341
342
343
344
345
346
347
348
349 public interface LaneChangePath
350 {
351
352 public final static LaneChangePath BEZIER = new LaneChangePath()
353 {
354 @Override
355 public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
356 final DirectedLanePosition from, final DirectedPoint startPosition,
357 final LateralDirectionality laneChangeDirection, final List<LaneDirection> fromLanes,
358 final List<LaneDirection> toLanes, final double endFractionalPosition, final Duration laneChangeDuration,
359 final double lcFraction) throws OTSGeometryException
360 {
361 DirectedPoint target = toLanes.get(toLanes.size() - 1).getLocationFraction(endFractionalPosition);
362 return Bezier.cubic(64, startPosition, target, 0.5);
363 }
364 };
365
366
367 public final static LaneChangePath SINE = new SequentialLaneChangePath()
368 {
369
370 @Override
371 protected double lateralFraction(final double lcFraction)
372 {
373 return -1.0 / (2 * Math.PI) * Math.sin(2 * Math.PI * lcFraction) + lcFraction;
374 }
375
376
377 @Override
378 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
379 {
380 return Math.atan((-width * Math.cos(2 * Math.PI * cumulLcLength / totalLcLength) / totalLcLength)
381 + width / totalLcLength);
382 }
383 };
384
385
386 public final static LaneChangePath POLY3 = new SequentialLaneChangePath()
387 {
388
389 @Override
390 protected double lateralFraction(final double lcFraction)
391 {
392 return 3 * (lcFraction * lcFraction) - 2 * (lcFraction * lcFraction * lcFraction);
393 }
394
395
396 @Override
397 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
398 {
399 return Math.atan(cumulLcLength * 6 * width / (totalLcLength * totalLcLength)
400 - cumulLcLength * cumulLcLength * 6 * width / (totalLcLength * totalLcLength * totalLcLength));
401 }
402 };
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 public abstract static class SequentialLaneChangePath implements LaneChangePath
418 {
419
420 @Override
421 public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
422 final DirectedLanePosition from, final DirectedPoint startPosition,
423 final LateralDirectionality laneChangeDirection, final List<LaneDirection> fromLanes,
424 final List<LaneDirection> toLanes, final double endFractionalPosition, final Duration laneChangeDuration,
425 final double lcFraction) throws OTSGeometryException
426 {
427 DirectedPoint toTarget = toLanes.get(toLanes.size() - 1).getLocationFraction(endFractionalPosition);
428 DirectedPoint fromTarget = fromLanes.get(fromLanes.size() - 1).getLocationFraction(endFractionalPosition);
429 double width = laneChangeDirection.isRight() ? fromTarget.distance(toTarget) : -fromTarget.distance(toTarget);
430 double dFraction = timeStep.si / laneChangeDuration.si;
431 return getPathRecursive(planDistance, meanSpeed, 1.0, width, from, startPosition, fromLanes, toLanes,
432 laneChangeDuration, lcFraction, dFraction);
433 }
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452 private OTSLine3D getPathRecursive(final Length planDistance, final Speed meanSpeed, final double buffer,
453 final double width, final DirectedLanePosition from, final DirectedPoint startPosition,
454 final List<LaneDirection> fromLanes, final List<LaneDirection> toLanes, final Duration laneChangeDuration,
455 final double lcFraction, final double dFraction) throws OTSGeometryException
456 {
457
458
459 double cutoff = (1.0 - lcFraction) / (dFraction * buffer);
460 cutoff = cutoff > 1.0 ? 1.0 : cutoff;
461
462
463 double lcFractionEnd = lcFraction + dFraction * buffer * cutoff;
464
465
466 double f = lateralFraction(lcFractionEnd);
467
468
469 double totalLcLength = meanSpeed.si * laneChangeDuration.si;
470 double cumulLcLength = totalLcLength * lcFractionEnd;
471
472
473 double positionAtEnd = (from.getGtuDirection().isPlus() ? from.getPosition().si
474 : from.getLane().getLength().si - from.getPosition().si) + planDistance.si * buffer * cutoff;
475 for (int i = 0; i < fromLanes.size(); i++)
476 {
477 LaneDirection fromLane = fromLanes.get(i);
478 if (fromLane.getLength().si >= positionAtEnd)
479 {
480
481 double endFraction = fromLane.fractionAtCoveredDistance(Length.createSI(positionAtEnd));
482 DirectedPoint pFrom = fromLane.getLocationFraction(endFraction);
483 DirectedPoint pTo = toLanes.get(i).getLocationFraction(endFraction);
484 DirectedPoint target = new DirectedPoint((1 - f) * pFrom.x + f * pTo.x, (1 - f) * pFrom.y + f * pTo.y,
485 (1 - f) * pFrom.z + f * pTo.z);
486
487 target.setRotZ(
488 (1 - f) * pFrom.getRotZ() + f * pTo.getRotZ() - angle(width, cumulLcLength, totalLcLength));
489
490 OTSLine3D path = Bezier.cubic(64, startPosition, target, 0.5);
491
492 if (path.getLength().si < planDistance.si && cutoff == 1.0)
493 {
494 return getPathRecursive(planDistance, meanSpeed, buffer * 1.25, width, from, startPosition,
495 fromLanes, toLanes, laneChangeDuration, lcFraction, dFraction);
496 }
497 return path;
498 }
499 positionAtEnd -= fromLane.getLength().si;
500 }
501 Throw.when(lcFraction + dFraction < 0.999, RuntimeException.class,
502 "No partial path for lane change could be determined; fromLanes are too short.");
503 return null;
504 }
505
506
507
508
509
510
511 protected abstract double lateralFraction(double lcFraction);
512
513
514
515
516
517
518
519
520
521 protected abstract double angle(double width, double cumulLcLength, double totalLcLength);
522 }
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543 OTSLine3D getPath(Duration timeStep, Length planDistance, Speed meanSpeed, DirectedLanePosition from,
544 DirectedPoint startPosition, LateralDirectionality laneChangeDirection, List<LaneDirection> fromLanes,
545 List<LaneDirection> toLanes, double endFractionalPosition, Duration laneChangeDuration, double lcFraction)
546 throws OTSGeometryException;
547 }
548 }