1 package org.opentrafficsim.road.gtu.lane.tactical;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashSet;
6 import java.util.List;
7 import java.util.Set;
8
9 import nl.tudelft.simulation.dsol.SimRuntimeException;
10 import nl.tudelft.simulation.language.d3.DirectedPoint;
11
12 import org.djunits.unit.AccelerationUnit;
13 import org.djunits.unit.SpeedUnit;
14 import org.djunits.unit.TimeUnit;
15 import org.djunits.value.vdouble.scalar.Acceleration;
16 import org.djunits.value.vdouble.scalar.Duration;
17 import org.djunits.value.vdouble.scalar.Length;
18 import org.djunits.value.vdouble.scalar.Speed;
19 import org.djunits.value.vdouble.scalar.Time;
20 import org.opentrafficsim.core.geometry.OTSGeometryException;
21 import org.opentrafficsim.core.geometry.OTSLine3D;
22 import org.opentrafficsim.core.geometry.OTSPoint3D;
23 import org.opentrafficsim.core.gtu.GTUDirectionality;
24 import org.opentrafficsim.core.gtu.GTUException;
25 import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
26 import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
27 import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
28 import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
29 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
30 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
31 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
32 import org.opentrafficsim.core.network.LateralDirectionality;
33 import org.opentrafficsim.core.network.NetworkException;
34 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
35 import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
36 import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultAlexander;
37 import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
38 import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedAltruistic;
39 import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedEgoistic;
40 import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneChangeModel;
41 import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneMovementStep;
42 import org.opentrafficsim.road.gtu.lane.tactical.following.AccelerationStep;
43 import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
44 import org.opentrafficsim.road.network.lane.Lane;
45 import org.opentrafficsim.road.network.lane.LaneDirection;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 public class LaneBasedGTUFollowingLaneChangeTacticalPlanner extends AbstractLaneBasedTacticalPlanner
79 {
80
81 private static final long serialVersionUID = 20160129L;
82
83
84 private static final double LANECHANGETIME = 2.0;
85
86
87 private Time earliestNexLaneChangeTime = Time.ZERO;
88
89
90 private static final double[] SCURVE;
91
92 static
93 {
94 SCURVE = new double[65];
95 for (int i = 0; i <= 64; i++)
96 {
97 double t = i / 64.0;
98 double ot = 1.0 - t;
99 double t3 = t * t * t;
100 SCURVE[i] = 10.0 * t3 * ot * ot + 5.0 * t3 * t * ot + t3 * t * t;
101 }
102 }
103
104
105 private GTUFollowingModelOld carFollowingModel;
106
107
108
109
110
111
112 public LaneBasedGTUFollowingLaneChangeTacticalPlanner(final GTUFollowingModelOld carFollowingModel,
113 final LaneBasedGTU gtu)
114 {
115 super(carFollowingModel, gtu);
116 }
117
118
119 @Override
120 public OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
121 throws OperationalPlanException, NetworkException, GTUException, ParameterException
122 {
123
124 LaneBasedGTU laneBasedGTU = getGtu();
125 LanePerception perception = getPerception();
126 BehavioralCharacteristics behaviorCharacteristics = laneBasedGTU.getBehavioralCharacteristics();
127 behaviorCharacteristics.setParameter(ParameterTypes.LOOKAHEAD, ParameterTypes.LOOKAHEAD.getDefaultValue());
128
129
130 laneBasedGTU.setTurnIndicatorStatus(TurnIndicatorStatus.NONE);
131
132
133 if (laneBasedGTU.getMaximumSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
134 {
135 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, TimeUnit.SECOND));
136 }
137
138
139 perception.getPerceptionCategory(DefaultAlexander.class).updateForwardHeadway();
140 perception.getPerceptionCategory(DefaultAlexander.class).updateAccessibleAdjacentLanesLeft();
141 perception.getPerceptionCategory(DefaultAlexander.class).updateAccessibleAdjacentLanesRight();
142 perception.getPerceptionCategory(DefaultAlexander.class).updateSpeedLimit();
143
144
145 Length forwardHeadway = behaviorCharacteristics.getParameter(ParameterTypes.LOOKAHEAD);
146 LanePathInfo lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway);
147 NextSplitInfo nextSplitInfo = determineNextSplit(laneBasedGTU, forwardHeadway);
148 Set<Lane> correctLanes = laneBasedGTU.getLanes().keySet();
149 correctLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
150
151
152 if (lanePathInfo.getPath().getLength().lt(forwardHeadway))
153 {
154 if (correctLanes.isEmpty())
155 {
156 LateralDirectionality direction = determineLeftRight(laneBasedGTU, nextSplitInfo);
157 if (direction != null)
158 {
159 getGtu().setTurnIndicatorStatus(
160 direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
161 OperationalPlan laneChangePlan =
162 makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, direction);
163 if (laneChangePlan != null)
164 {
165 return laneChangePlan;
166 }
167 }
168 }
169 }
170
171
172 if (getGtu().getSimulator().getSimulatorTime().getTime().lt(this.earliestNexLaneChangeTime))
173 {
174 return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
175 }
176
177
178
179 Set<Lane> leftLanes =
180 perception.getPerceptionCategory(DefaultAlexander.class).getAccessibleAdjacentLanesLeft().get(
181 lanePathInfo.getReferenceLane());
182 if (nextSplitInfo.isSplit())
183 {
184 leftLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
185 }
186 if (!leftLanes.isEmpty() && laneBasedGTU.getSpeed().si > 4.0)
187 {
188 perception.getPerceptionCategory(DefaultAlexander.class).updateBackwardHeadway();
189 perception.getPerceptionCategory(DefaultAlexander.class).updateParallelHeadwaysLeft();
190 perception.getPerceptionCategory(DefaultAlexander.class).updateNeighboringHeadwaysLeft();
191 if (perception.getPerceptionCategory(DefaultAlexander.class).getParallelHeadwaysLeft().isEmpty())
192 {
193 Collection<Headway> sameLaneTraffic = new HashSet<>();
194
195 if (perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway() != null
196 && perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getObjectType().isGtu())
197 {
198 sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway());
199 }
200 if (perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway() != null
201 && perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway().getObjectType().isGtu())
202 {
203 sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway());
204 }
205 DirectedLaneChangeModel dlcm = new DirectedAltruistic(getPerception());
206 DirectedLaneMovementStep dlms =
207 dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.LEFT, sameLaneTraffic,
208 perception.getPerceptionCategory(DefaultAlexander.class).getNeighboringHeadwaysLeft(),
209 behaviorCharacteristics.getParameter(ParameterTypes.LOOKAHEAD), perception.getPerceptionCategory(
210 DefaultAlexander.class).getSpeedLimit(), new Acceleration(1.0, AccelerationUnit.SI),
211 new Acceleration(0.5, AccelerationUnit.SI), new Duration(LANECHANGETIME, TimeUnit.SECOND));
212 if (dlms.getLaneChange() != null)
213 {
214 getGtu().setTurnIndicatorStatus(TurnIndicatorStatus.LEFT);
215 OperationalPlan laneChangePlan =
216 makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.LEFT);
217 if (laneChangePlan != null)
218 {
219 return laneChangePlan;
220 }
221 }
222 }
223 }
224
225
226 Set<Lane> rightLanes =
227 perception.getPerceptionCategory(DefaultAlexander.class).getAccessibleAdjacentLanesRight().get(
228 lanePathInfo.getReferenceLane());
229 if (nextSplitInfo.isSplit())
230 {
231 rightLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
232 }
233 if (!rightLanes.isEmpty() && laneBasedGTU.getSpeed().si > 4.0)
234 {
235 perception.getPerceptionCategory(DefaultAlexander.class).updateBackwardHeadway();
236 perception.getPerceptionCategory(DefaultAlexander.class).updateParallelHeadwaysRight();
237 perception.getPerceptionCategory(DefaultAlexander.class).updateNeighboringHeadwaysRight();
238 if (perception.getPerceptionCategory(DefaultAlexander.class).getParallelHeadwaysRight().isEmpty())
239 {
240
241 Collection<Headway> sameLaneTraffic = new HashSet<>();
242 if (perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway() != null
243 && perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getObjectType().isGtu())
244 {
245 sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway());
246 }
247 if (perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway() != null
248 && perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway().getObjectType().isGtu())
249 {
250 sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway());
251 }
252 DirectedLaneChangeModel dlcm = new DirectedAltruistic(getPerception());
253 DirectedLaneMovementStep dlms =
254 dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.RIGHT, sameLaneTraffic,
255 perception.getPerceptionCategory(DefaultAlexander.class).getNeighboringHeadwaysRight(),
256 behaviorCharacteristics.getParameter(ParameterTypes.LOOKAHEAD), perception.getPerceptionCategory(
257 DefaultAlexander.class).getSpeedLimit(), new Acceleration(1.0, AccelerationUnit.SI),
258 new Acceleration(0.5, AccelerationUnit.SI), new Duration(LANECHANGETIME, TimeUnit.SECOND));
259 if (dlms.getLaneChange() != null)
260 {
261 getGtu().setTurnIndicatorStatus(TurnIndicatorStatus.RIGHT);
262 OperationalPlan laneChangePlan =
263 makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.RIGHT);
264 if (laneChangePlan != null)
265 {
266 return laneChangePlan;
267 }
268 }
269 }
270 }
271
272 return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
273 }
274
275
276
277
278
279
280
281
282
283
284
285 private OperationalPlan currentLanePlan(final LaneBasedGTU laneBasedGTU, final Time startTime,
286 final DirectedPoint locationAtStartTime, final LanePathInfo lanePathInfo) throws OperationalPlanException,
287 GTUException
288 {
289 LanePerception perception = getPerception();
290
291
292 AccelerationStep accelerationStep;
293 Headway headway = perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway();
294 Length maxDistance = lanePathInfo.getPath().getLength().minus(laneBasedGTU.getLength().multiplyBy(2.0));
295 accelerationStep =
296 this.carFollowingModel.computeAccelerationStep(laneBasedGTU, headway.getSpeed(), headway.getDistance(),
297 maxDistance, perception.getPerceptionCategory(DefaultAlexander.class).getSpeedLimit());
298
299
300 if (accelerationStep.getAcceleration().si < 1E-6 && laneBasedGTU.getSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
301 {
302 return new OperationalPlan(laneBasedGTU, locationAtStartTime, startTime, accelerationStep.getDuration());
303 }
304
305
306 List<Segment> operationalPlanSegmentList = new ArrayList<>();
307 if (accelerationStep.getAcceleration().si == 0.0)
308 {
309 Segment segment = new OperationalPlan.SpeedSegment(accelerationStep.getDuration());
310 operationalPlanSegmentList.add(segment);
311 }
312 else
313 {
314 Segment segment =
315 new OperationalPlan.AccelerationSegment(accelerationStep.getDuration(), accelerationStep.getAcceleration());
316 operationalPlanSegmentList.add(segment);
317 }
318 OperationalPlan op =
319 new OperationalPlan(laneBasedGTU, lanePathInfo.getPath(), startTime, laneBasedGTU.getSpeed(),
320 operationalPlanSegmentList);
321 return op;
322 }
323
324
325
326
327
328
329
330 private LateralDirectionality determineLeftRight(final LaneBasedGTU laneBasedGTU, final NextSplitInfo nextSplitInfo)
331 {
332
333 for (Lane correctLane : nextSplitInfo.getCorrectCurrentLanes())
334 {
335 for (Lane currentLane : laneBasedGTU.getLanes().keySet())
336 {
337 if (correctLane.getParentLink().equals(currentLane.getParentLink()))
338 {
339 double deltaOffset =
340 correctLane.getDesignLineOffsetAtBegin().si - currentLane.getDesignLineOffsetAtBegin().si;
341 if (laneBasedGTU.getLanes().get(currentLane).equals(GTUDirectionality.DIR_PLUS))
342 {
343 return deltaOffset > 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
344 }
345 else
346 {
347 return deltaOffset < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
348 }
349 }
350 }
351 }
352 return null;
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 private OperationalPlan makeLaneChangePlanMobil(final LaneBasedGTU gtu, final LanePerception perception,
369 final LanePathInfo lanePathInfo, final LateralDirectionality direction) throws GTUException, NetworkException,
370 ParameterException, OperationalPlanException
371 {
372 Collection<Headway> otherLaneTraffic;
373 perception.getPerceptionCategory(DefaultAlexander.class).updateForwardHeadway();
374 perception.getPerceptionCategory(DefaultAlexander.class).updateBackwardHeadway();
375 if (direction.isLeft())
376 {
377 perception.getPerceptionCategory(DefaultAlexander.class).updateParallelHeadwaysLeft();
378 perception.getPerceptionCategory(DefaultAlexander.class).updateNeighboringHeadwaysLeft();
379 otherLaneTraffic = perception.getPerceptionCategory(DefaultAlexander.class).getNeighboringHeadwaysLeft();
380 }
381 else if (direction.isRight())
382 {
383 perception.getPerceptionCategory(DefaultAlexander.class).updateParallelHeadwaysRight();
384 perception.getPerceptionCategory(DefaultAlexander.class).updateNeighboringHeadwaysRight();
385 otherLaneTraffic = perception.getPerceptionCategory(DefaultAlexander.class).getNeighboringHeadwaysRight();
386 }
387 else
388 {
389 throw new GTUException("lateral direction during lane change neither LEFT nor RIGHT");
390 }
391 if (!perception.getPerceptionCategory(DefaultAlexander.class).getParallelHeadways(direction).isEmpty())
392 {
393 return null;
394 }
395
396 Collection<Headway> sameLaneTraffic = new HashSet<>();
397
398 if (perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway() != null
399 && perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getObjectType().isGtu())
400 {
401 sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway());
402 }
403 if (perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway() != null
404 && perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway().getObjectType().isGtu())
405 {
406 sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway());
407 }
408
409
410
411
412
413
414
415 DirectedLaneChangeModel dlcm = new DirectedEgoistic(getPerception());
416
417 DirectedLaneMovementStep dlms =
418 dlcm.computeLaneChangeAndAcceleration(gtu, direction, sameLaneTraffic, otherLaneTraffic, gtu
419 .getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD), perception.getPerceptionCategory(
420 DefaultAlexander.class).getSpeedLimit(), new Acceleration(2.0, AccelerationUnit.SI), new Acceleration(0.1,
421 AccelerationUnit.SI), new Duration(LANECHANGETIME, TimeUnit.SECOND));
422 if (dlms.getLaneChange() == null)
423 {
424 return null;
425 }
426
427 Lane startLane = getReferenceLane(gtu);
428 Set<Lane> adjacentLanes = startLane.accessibleAdjacentLanes(direction, gtu.getGTUType());
429
430 Lane adjacentLane = adjacentLanes.iterator().next();
431 Length startPosition = gtu.position(startLane, gtu.getReference());
432 double fraction2 = startLane.fraction(startPosition);
433 LanePathInfo lanePathInfo2 =
434 buildLanePathInfo(gtu, gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD), adjacentLane,
435 fraction2, gtu.getLanes().get(startLane));
436
437
438 AccelerationStep accelerationStep = dlms.getGfmr();
439 Speed v0 = gtu.getSpeed();
440 double t = accelerationStep.getDuration().si;
441 double distanceSI = v0.si * t + 0.5 * accelerationStep.getAcceleration().si * t * t;
442 Speed vt = v0.plus(accelerationStep.getAcceleration().multiplyBy(accelerationStep.getDuration()));
443
444
445
446 if (distanceSI < 2.0)
447 {
448 return null;
449 }
450
451
452 if (perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway() == null
453 || (perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway() != null && perception
454 .getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getDistance().si < 5.0))
455 {
456 return null;
457 }
458
459 OTSLine3D path;
460 try
461 {
462 path = interpolateScurve(lanePathInfo.getPath(), lanePathInfo2.getPath(), distanceSI);
463 }
464 catch (OTSGeometryException exception)
465 {
466 System.err.println("GTU : " + gtu);
467 System.err.println("LanePathInfo : " + lanePathInfo.getPath());
468 System.err.println("LanePathInfo2: " + lanePathInfo2.getPath());
469 System.err.println("distanceSI : " + distanceSI);
470 System.err.println("v0, t, vt, a : " + v0 + ", " + t + ", " + vt + ", " + accelerationStep.getAcceleration());
471 throw new GTUException(exception);
472 }
473
474 try
475 {
476 double a = accelerationStep.getAcceleration().si;
477
478 if (path.getLengthSI() > distanceSI * 1.5)
479 {
480 a = (path.getLengthSI() - v0.si) / LANECHANGETIME;
481 vt = new Speed(v0.si + LANECHANGETIME * a, SpeedUnit.SI);
482 }
483
484
485
486 for (Lane lane : gtu.getLanes().keySet())
487 {
488 gtu.getSimulator().scheduleEventRel(new Duration(LANECHANGETIME - 0.001, TimeUnit.SI), this, gtu,
489 "leaveLane", new Object[] {lane});
490 }
491
492
493 for (LaneDirection laneDirection : lanePathInfo.getLaneDirectionList())
494 {
495 if (!gtu.getLanes().keySet().contains(laneDirection.getLane()))
496 {
497 gtu.getSimulator().scheduleEventRel(new Duration(LANECHANGETIME - 0.001, TimeUnit.SI), this, gtu,
498 "leaveLane", new Object[] {laneDirection.getLane()});
499 }
500 }
501
502 gtu.enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fraction2), gtu.getLanes().get(startLane));
503 System.out.println("gtu " + gtu.getId() + " entered lane " + adjacentLane + " at pos "
504 + adjacentLane.getLength().multiplyBy(fraction2));
505
506 List<Segment> operationalPlanSegmentList = new ArrayList<>();
507 if (a == 0.0)
508 {
509 Segment segment = new OperationalPlan.SpeedSegment(new Duration(LANECHANGETIME, TimeUnit.SI));
510 operationalPlanSegmentList.add(segment);
511 }
512 else
513 {
514 Segment segment =
515 new OperationalPlan.AccelerationSegment(new Duration(LANECHANGETIME, TimeUnit.SI), new Acceleration(a,
516 AccelerationUnit.SI));
517 operationalPlanSegmentList.add(segment);
518 }
519 OperationalPlan op =
520 new OperationalPlan(gtu, path, gtu.getSimulator().getSimulatorTime().getTime(), v0,
521 operationalPlanSegmentList);
522 this.earliestNexLaneChangeTime =
523 gtu.getSimulator().getSimulatorTime().getTime().plus(new Duration(17, TimeUnit.SECOND));
524
525
526 gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
527
528 return op;
529 }
530 catch (OperationalPlanException | SimRuntimeException exception)
531 {
532 throw new GTUException(exception);
533 }
534 }
535
536
537
538
539
540
541
542
543
544 private static OTSLine3D interpolateLinear(final OTSLine3D line1, final OTSLine3D line2, final double lengthSI)
545 throws OTSGeometryException
546 {
547 OTSLine3D l1 = line1.extract(0, lengthSI);
548 OTSLine3D l2 = line2.extract(0, lengthSI);
549 List<OTSPoint3D> line = new ArrayList<>();
550 int num = 32;
551 for (int i = 0; i <= num; i++)
552 {
553 double f0 = 1.0 * i / num;
554 double f1 = 1.0 - f0;
555 DirectedPoint p1 = l1.getLocationFraction(f0);
556 DirectedPoint p2 = l2.getLocationFraction(f0);
557 line.add(new OTSPoint3D(p1.x * f1 + p2.x * f0, p1.y * f1 + p2.y * f0, p1.z * f1 + p2.z * f0));
558 }
559 return new OTSLine3D(line);
560 }
561
562
563
564
565
566
567
568
569
570 private static OTSLine3D interpolateScurve(final OTSLine3D line1, final OTSLine3D line2, final double lengthSI)
571 throws OTSGeometryException
572 {
573 OTSLine3D l1 = line1.extract(0, lengthSI);
574 OTSLine3D l2 = line2.extract(0, lengthSI);
575 List<OTSPoint3D> line = new ArrayList<>();
576 int num = 64;
577 for (int i = 0; i <= num; i++)
578 {
579 double factor = SCURVE[i];
580 DirectedPoint p1 = l1.getLocationFraction(i / 64.0);
581 DirectedPoint p2 = l2.getLocationFraction(i / 64.0);
582 line.add(new OTSPoint3D(p1.x + factor * (p2.x - p1.x), p1.y + factor * (p2.y - p1.y), p1.z + factor
583 * (p2.z - p1.z)));
584 }
585 return new OTSLine3D(line);
586 }
587
588
589 @Override
590 public final String toString()
591 {
592 return "LaneBasedGTUFollowingLaneChangeTacticalPlanner [earliestNexLaneChangeTime=" + this.earliestNexLaneChangeTime
593 + ", carFollowingModel=" + this.carFollowingModel + "]";
594 }
595 }