1 package org.opentrafficsim.road.gtu.lane.tactical;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.LinkedHashMap;
6 import java.util.List;
7 import java.util.Map;
8
9 import org.djunits.unit.AccelerationUnit;
10 import org.djunits.unit.DurationUnit;
11 import org.djunits.unit.LengthUnit;
12 import org.djunits.value.ValueRuntimeException;
13 import org.djunits.value.storage.StorageType;
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.djunits.value.vdouble.vector.AccelerationVector;
20 import org.djunits.value.vdouble.vector.base.DoubleVector;
21 import org.opentrafficsim.base.parameters.ParameterException;
22 import org.opentrafficsim.base.parameters.ParameterTypeLength;
23 import org.opentrafficsim.base.parameters.ParameterTypes;
24 import org.opentrafficsim.core.geometry.DirectedPoint;
25 import org.opentrafficsim.core.geometry.OtsGeometryException;
26 import org.opentrafficsim.core.geometry.OtsLine3d;
27 import org.opentrafficsim.core.gtu.GtuException;
28 import org.opentrafficsim.core.gtu.GtuType;
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.Link;
34 import org.opentrafficsim.core.network.NetworkException;
35 import org.opentrafficsim.core.network.Node;
36 import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
37 import org.opentrafficsim.road.gtu.lane.perception.CategoricalLanePerception;
38 import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
39 import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultSimplePerception;
40 import org.opentrafficsim.road.gtu.lane.perception.categories.DirectDefaultSimplePerception;
41 import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
42 import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayTrafficLight;
43 import org.opentrafficsim.road.gtu.lane.tactical.following.GtuFollowingModelOld;
44 import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneChangeModel;
45 import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneMovementStep;
46 import org.opentrafficsim.road.network.lane.CrossSectionElement;
47 import org.opentrafficsim.road.network.lane.CrossSectionLink;
48 import org.opentrafficsim.road.network.lane.Lane;
49 import org.opentrafficsim.road.network.lane.LanePosition;
50 import org.opentrafficsim.road.network.lane.object.detector.LaneDetector;
51 import org.opentrafficsim.road.network.lane.object.detector.SinkDetector;
52
53
54
55
56
57
58
59
60
61
62
63
64 public class LaneBasedCfLcTacticalPlanner extends AbstractLaneBasedTacticalPlanner
65 {
66
67 private static final long serialVersionUID = 20151125L;
68
69
70 protected static final ParameterTypeLength LOOKBACKOLD = ParameterTypes.LOOKBACKOLD;
71
72
73 private static final Acceleration STAYINCURRENTLANEINCENTIVE = new Acceleration(0.1, AccelerationUnit.METER_PER_SECOND_2);
74
75
76 private static final Acceleration PREFERREDLANEINCENTIVE = new Acceleration(0.3, AccelerationUnit.METER_PER_SECOND_2);
77
78
79 private static final Acceleration NONPREFERREDLANEINCENTIVE = new Acceleration(-0.3, AccelerationUnit.METER_PER_SECOND_2);
80
81
82 public static final Length NOLANECHANGENEEDED = new Length(Double.MAX_VALUE, LengthUnit.SI);
83
84
85 public static final Length GETOFFTHISLANENOW = Length.ZERO;
86
87
88 private static final Duration TIMEHORIZON = new Duration(90, DurationUnit.SECOND);
89
90
91 private LaneChangeModel laneChangeModel;
92
93
94
95
96
97
98
99 public LaneBasedCfLcTacticalPlanner(final GtuFollowingModelOld carFollowingModel, final LaneChangeModel laneChangeModel,
100 final LaneBasedGtu gtu)
101 {
102 super(carFollowingModel, gtu, new CategoricalLanePerception(gtu));
103 this.laneChangeModel = laneChangeModel;
104 getPerception().addPerceptionCategory(new DirectDefaultSimplePerception(getPerception()));
105 }
106
107
108 @Override
109 public final OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
110 throws OperationalPlanException, NetworkException, GtuException, ParameterException
111 {
112 try
113 {
114
115 LaneBasedGtu laneBasedGTU = getGtu();
116 LanePerception perception = getPerception();
117
118
119 if (laneBasedGTU.getMaximumSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
120 {
121 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, DurationUnit.SECOND));
122 }
123
124 Length maximumForwardHeadway = laneBasedGTU.getParameters().getParameter(LOOKAHEAD);
125 DefaultSimplePerception simplePerception = perception.getPerceptionCategory(DefaultSimplePerception.class);
126 Speed speedLimit = simplePerception.getSpeedLimit();
127
128
129 Headway sameLaneLeader = simplePerception.getForwardHeadwayGtu();
130
131 Headway sameLaneFollower = simplePerception.getBackwardHeadway();
132 Collection<Headway> sameLaneTraffic = new ArrayList<>();
133 if (sameLaneLeader.getObjectType().isGtu())
134 {
135 sameLaneTraffic.add(sameLaneLeader);
136 }
137 if (sameLaneFollower.getObjectType().isGtu())
138 {
139 sameLaneTraffic.add(sameLaneFollower);
140 }
141
142
143 LanePathInfo lanePathInfo = buildLanePathInfo(laneBasedGTU, maximumForwardHeadway);
144
145
146 NextSplitInfo nextSplitInfo = determineNextSplit(laneBasedGTU, maximumForwardHeadway);
147 boolean currentLaneFine = nextSplitInfo.getCorrectCurrentLanes().contains(lanePathInfo.getReferenceLane());
148
149
150
151
152
153 Collection<Headway> leftLaneTraffic = simplePerception.getNeighboringHeadwaysLeft();
154 Collection<Headway> rightLaneTraffic = simplePerception.getNeighboringHeadwaysRight();
155
156
157 final LateralDirectionality preferred = LateralDirectionality.RIGHT;
158 final Acceleration defaultLeftLaneIncentive =
159 preferred.isLeft() ? PREFERREDLANEINCENTIVE : NONPREFERREDLANEINCENTIVE;
160 final Acceleration defaultRightLaneIncentive =
161 preferred.isRight() ? PREFERREDLANEINCENTIVE : NONPREFERREDLANEINCENTIVE;
162
163 AccelerationVector defaultLaneIncentives =
164 DoubleVector.instantiate(new double[] {defaultLeftLaneIncentive.getSI(), STAYINCURRENTLANEINCENTIVE.getSI(),
165 defaultRightLaneIncentive.getSI()}, AccelerationUnit.SI, StorageType.DENSE);
166 AccelerationVector laneIncentives = laneIncentives(laneBasedGTU, defaultLaneIncentives);
167 LaneMovementStep lcmr = this.laneChangeModel.computeLaneChangeAndAcceleration(laneBasedGTU, sameLaneTraffic,
168 rightLaneTraffic, leftLaneTraffic, speedLimit,
169 new Acceleration(laneIncentives.get(preferred.isRight() ? 2 : 0)), new Acceleration(laneIncentives.get(1)),
170 new Acceleration(laneIncentives.get(preferred.isRight() ? 0 : 2)));
171 Duration duration = lcmr.getGfmr().getValidUntil().minus(getGtu().getSimulator().getSimulatorAbsTime());
172 if (lcmr.getLaneChangeDirection() != null)
173 {
174 laneBasedGTU.changeLaneInstantaneously(lcmr.getLaneChangeDirection());
175
176
177 lanePathInfo = buildLanePathInfo(laneBasedGTU, maximumForwardHeadway);
178 }
179
180
181 Headway object = simplePerception.getForwardHeadwayObject();
182 Acceleration a = lcmr.getGfmr().getAcceleration();
183 if (object instanceof HeadwayTrafficLight)
184 {
185
186 a = Acceleration.min(a, ((GtuFollowingModelOld) getCarFollowingModel()).computeAcceleration(getGtu().getSpeed(),
187 getGtu().getMaximumSpeed(), Speed.ZERO, object.getDistance(), speedLimit));
188 }
189
190
191 Length dist = lanePathInfo.getPath().getLength().minus(getGtu().getFront().getDx());
192 a = Acceleration.min(a, ((GtuFollowingModelOld) getCarFollowingModel()).computeAcceleration(getGtu().getSpeed(),
193 getGtu().getMaximumSpeed(), Speed.ZERO, dist, speedLimit));
194
195
196 OtsLine3d path = lanePathInfo.getPath();
197 if (a.si < 1E-6 && laneBasedGTU.getSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
198 {
199 try
200 {
201 return new OperationalPlan(getGtu(), path.getLocationFraction(0.0), startTime, duration);
202 }
203 catch (OtsGeometryException exception)
204 {
205
206 throw new RuntimeException(exception);
207 }
208 }
209 List<Segment> operationalPlanSegmentList = new ArrayList<>();
210
211 if (a.si == 0.0)
212 {
213 Segment segment = new OperationalPlan.SpeedSegment(duration);
214 operationalPlanSegmentList.add(segment);
215 }
216 else
217 {
218 Segment segment = new OperationalPlan.AccelerationSegment(duration, a);
219 operationalPlanSegmentList.add(segment);
220 }
221 OperationalPlan op =
222 new OperationalPlan(getGtu(), path, startTime, getGtu().getSpeed(), operationalPlanSegmentList);
223 return op;
224 }
225 catch (ValueRuntimeException exception)
226 {
227 throw new GtuException(exception);
228 }
229 }
230
231
232
233
234
235
236
237
238
239
240
241
242
243 private AccelerationVector laneIncentives(final LaneBasedGtu gtu, final AccelerationVector defaultLaneIncentives)
244 throws NetworkException, ValueRuntimeException, GtuException, OperationalPlanException
245 {
246 Length leftSuitability = suitability(gtu, LateralDirectionality.LEFT);
247 Length currentSuitability = suitability(gtu, null);
248 Length rightSuitability = suitability(gtu, LateralDirectionality.RIGHT);
249 if (leftSuitability == NOLANECHANGENEEDED && currentSuitability == NOLANECHANGENEEDED
250 && rightSuitability == NOLANECHANGENEEDED)
251 {
252 return checkLaneDrops(gtu, defaultLaneIncentives);
253 }
254 if ((leftSuitability == NOLANECHANGENEEDED || leftSuitability == GETOFFTHISLANENOW)
255 && currentSuitability == NOLANECHANGENEEDED
256 && (rightSuitability == NOLANECHANGENEEDED || rightSuitability == GETOFFTHISLANENOW))
257 {
258 return checkLaneDrops(gtu,
259 DoubleVector.instantiate(new double[] {acceleration(gtu, leftSuitability),
260 defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability)}, AccelerationUnit.SI,
261 StorageType.DENSE));
262 }
263 if (currentSuitability == NOLANECHANGENEEDED)
264 {
265 return DoubleVector.instantiate(new double[] {acceleration(gtu, leftSuitability),
266 defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability)}, AccelerationUnit.SI,
267 StorageType.DENSE);
268 }
269 return DoubleVector.instantiate(new double[] {acceleration(gtu, leftSuitability), acceleration(gtu, currentSuitability),
270 acceleration(gtu, rightSuitability)}, AccelerationUnit.SI, StorageType.DENSE);
271 }
272
273
274
275
276
277
278
279
280
281
282
283
284
285 private AccelerationVector checkLaneDrops(final LaneBasedGtu gtu, final AccelerationVector defaultLaneIncentives)
286 throws NetworkException, ValueRuntimeException, GtuException, OperationalPlanException
287 {
288
289 Length leftSuitability = Double.isNaN(defaultLaneIncentives.get(0).si) || defaultLaneIncentives.get(0).si < -10
290 ? GETOFFTHISLANENOW : laneDrop(gtu, LateralDirectionality.LEFT);
291 Length currentSuitability = laneDrop(gtu, null);
292 Length rightSuitability = Double.isNaN(defaultLaneIncentives.get(2).si) || defaultLaneIncentives.get(2).si < -10
293 ? GETOFFTHISLANENOW : laneDrop(gtu, LateralDirectionality.RIGHT);
294
295 if ((leftSuitability == NOLANECHANGENEEDED || leftSuitability == GETOFFTHISLANENOW)
296 && currentSuitability == NOLANECHANGENEEDED
297 && (rightSuitability == NOLANECHANGENEEDED || rightSuitability == GETOFFTHISLANENOW))
298 {
299 return defaultLaneIncentives;
300 }
301
302 if (currentSuitability == NOLANECHANGENEEDED)
303 {
304 return DoubleVector.instantiate(new double[] {acceleration(gtu, leftSuitability),
305 defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability)}, AccelerationUnit.SI,
306 StorageType.DENSE);
307 }
308 if (currentSuitability.le(leftSuitability))
309 {
310 return DoubleVector.instantiate(
311 new double[] {PREFERREDLANEINCENTIVE.getSI(), NONPREFERREDLANEINCENTIVE.getSI(), GETOFFTHISLANENOW.getSI()},
312 AccelerationUnit.SI, StorageType.DENSE);
313 }
314 if (currentSuitability.le(rightSuitability))
315 {
316 return DoubleVector.instantiate(
317 new double[] {GETOFFTHISLANENOW.getSI(), NONPREFERREDLANEINCENTIVE.getSI(), PREFERREDLANEINCENTIVE.getSI()},
318 AccelerationUnit.SI, StorageType.DENSE);
319 }
320 return DoubleVector.instantiate(new double[] {acceleration(gtu, leftSuitability), acceleration(gtu, currentSuitability),
321 acceleration(gtu, rightSuitability)}, AccelerationUnit.SI, StorageType.DENSE);
322 }
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337 private Length laneDrop(final LaneBasedGtu gtu, final LateralDirectionality direction)
338 throws NetworkException, GtuException, OperationalPlanException
339 {
340 LanePosition dlp = gtu.getReferencePosition();
341 Lane lane = dlp.getLane();
342 Length longitudinalPosition = dlp.getPosition();
343 if (null != direction)
344 {
345 lane = getPerception().getPerceptionCategory(DefaultSimplePerception.class).bestAccessibleAdjacentLane(lane,
346 direction, longitudinalPosition);
347 }
348 if (null == lane)
349 {
350 return GETOFFTHISLANENOW;
351 }
352 double remainingLength = lane.getLength().getSI() - longitudinalPosition.getSI();
353 double remainingTimeSI = TIMEHORIZON.getSI() - remainingLength / lane.getSpeedLimit(gtu.getType()).getSI();
354 while (remainingTimeSI >= 0)
355 {
356 for (LaneDetector detector : lane.getDetectors())
357 {
358 if (detector instanceof SinkDetector)
359 {
360 return NOLANECHANGENEEDED;
361 }
362 }
363 int branching = lane.nextLanes(gtu.getType()).size();
364 if (branching == 0)
365 {
366 return new Length(remainingLength, LengthUnit.SI);
367 }
368 if (branching > 1)
369 {
370 return NOLANECHANGENEEDED;
371 }
372 lane = lane.nextLanes(gtu.getType()).iterator().next();
373 remainingTimeSI -= lane.getLength().getSI() / lane.getSpeedLimit(gtu.getType()).getSI();
374 remainingLength += lane.getLength().getSI();
375 }
376 return NOLANECHANGENEEDED;
377 }
378
379
380
381
382
383
384
385
386
387
388
389
390 private Length suitability(final LaneBasedGtu gtu, final LateralDirectionality direction)
391 throws NetworkException, GtuException, OperationalPlanException
392 {
393 LanePosition dlp = gtu.getReferencePosition();
394 Lane lane = dlp.getLane();
395 Length longitudinalPosition = dlp.getPosition().plus(gtu.getFront().getDx());
396 if (null != direction)
397 {
398 lane = getPerception().getPerceptionCategory(DefaultSimplePerception.class).bestAccessibleAdjacentLane(lane,
399 direction, longitudinalPosition);
400 }
401 if (null == lane)
402 {
403 return GETOFFTHISLANENOW;
404 }
405 try
406 {
407 return suitability(lane, longitudinalPosition, gtu, TIMEHORIZON);
408
409 }
410 catch (NetworkException ne)
411 {
412 System.err.println(gtu + " has a route problem in suitability: " + ne.getMessage());
413 return NOLANECHANGENEEDED;
414 }
415 }
416
417
418
419
420
421
422
423 private double acceleration(final LaneBasedGtu gtu, final Length stopDistance)
424 {
425
426
427 double v = gtu.getSpeed().getSI();
428 double a = -v * v / 2 / stopDistance.getSI();
429 return a;
430 }
431
432
433
434
435
436
437
438
439
440
441
442
443 private Length suitability(final Lane lane, final Length longitudinalPosition, final LaneBasedGtu gtu,
444 final Duration timeHorizon) throws NetworkException
445 {
446 double remainingDistance = lane.getLength().getSI() - longitudinalPosition.getSI();
447 double spareTime = timeHorizon.getSI() - remainingDistance / lane.getSpeedLimit(gtu.getType()).getSI();
448
449 Node nextNode = lane.getParentLink().getEndNode();
450 Link lastLink = lane.getParentLink();
451 Node nextSplitNode = null;
452 Lane currentLane = lane;
453 CrossSectionLink linkBeforeBranch = lane.getParentLink();
454 while (null != nextNode)
455 {
456 if (spareTime <= 0)
457 {
458 return NOLANECHANGENEEDED;
459 }
460 int laneCount = countCompatibleLanes(linkBeforeBranch, gtu.getType());
461 if (0 == laneCount)
462 {
463 throw new NetworkException("No compatible Lanes on Link " + linkBeforeBranch);
464 }
465 if (1 == laneCount)
466 {
467 return NOLANECHANGENEEDED;
468
469 }
470 int branching = nextNode.getLinks().size();
471 if (branching > 2)
472 {
473 nextSplitNode = nextNode;
474 break;
475 }
476 else if (1 == branching)
477 {
478 return NOLANECHANGENEEDED;
479 }
480 else
481 {
482 Link nextLink = gtu.getStrategicalPlanner().nextLink(lastLink, gtu.getType());
483 if (nextLink instanceof CrossSectionLink)
484 {
485 nextNode = nextLink.getEndNode();
486
487 remainingDistance += nextLink.getLength().getSI();
488 linkBeforeBranch = (CrossSectionLink) nextLink;
489
490 if (currentLane.nextLanes(gtu.getType()).size() == 0)
491 {
492
493
494 if (currentLane.accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, gtu.getType()).size() > 0)
495 {
496 for (Lane adjacentLane : currentLane.accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT,
497 gtu.getType()))
498 {
499 if (adjacentLane.nextLanes(gtu.getType()).size() > 0)
500 {
501 currentLane = adjacentLane;
502 break;
503 }
504
505
506 }
507 }
508 for (Lane adjacentLane : currentLane.accessibleAdjacentLanesLegal(LateralDirectionality.LEFT,
509 gtu.getType()))
510 {
511 if (adjacentLane.nextLanes(gtu.getType()).size() > 0)
512 {
513 currentLane = adjacentLane;
514 break;
515 }
516
517
518 }
519 if (currentLane.nextLanes(gtu.getType()).size() == 0)
520 {
521 throw new NetworkException(
522 "Lane ends and there is not a compatible adjacent lane that does " + "not end");
523 }
524 }
525
526 for (Lane nextLane : currentLane.nextLanes(gtu.getType()))
527 {
528 currentLane = currentLane.nextLanes(gtu.getType()).iterator().next();
529 break;
530 }
531 spareTime -= currentLane.getLength().getSI() / currentLane.getSpeedLimit(gtu.getType()).getSI();
532 }
533 else
534 {
535
536
537 return NOLANECHANGENEEDED;
538 }
539 lastLink = nextLink;
540 }
541 }
542 if (null == nextNode)
543 {
544 throw new NetworkException("Cannot find the next branch or sink node");
545 }
546
547
548 Map<Lane, Length> suitabilityOfLanesBeforeBranch = new LinkedHashMap<>();
549 Link linkAfterBranch = gtu.getStrategicalPlanner().nextLink(lastLink, gtu.getType());
550 for (CrossSectionElement cse : linkBeforeBranch.getCrossSectionElementList())
551 {
552 if (cse instanceof Lane)
553 {
554 Lane l = (Lane) cse;
555 if (l.getType().isCompatible(gtu.getType()))
556 {
557 for (Lane connectingLane : l.nextLanes(gtu.getType()))
558 {
559 if (connectingLane.getParentLink() == linkAfterBranch
560 && connectingLane.getType().isCompatible(gtu.getType()))
561 {
562 Length currentValue = suitabilityOfLanesBeforeBranch.get(l);
563
564
565
566 Length value = suitability(connectingLane, new Length(0, LengthUnit.SI), gtu,
567 new Duration(spareTime, DurationUnit.SI));
568
569 value = value.plus(new Length(remainingDistance, LengthUnit.SI));
570
571
572 suitabilityOfLanesBeforeBranch.put(l,
573 null == currentValue || value.le(currentValue) ? value : currentValue);
574 }
575 }
576 }
577 }
578 }
579 if (suitabilityOfLanesBeforeBranch.size() == 0)
580 {
581 throw new NetworkException("No lanes available on Link " + linkBeforeBranch);
582 }
583 Length currentLaneSuitability = suitabilityOfLanesBeforeBranch.get(currentLane);
584 if (null != currentLaneSuitability)
585 {
586 return currentLaneSuitability;
587 }
588
589 int totalLanes = countCompatibleLanes(currentLane.getParentLink(), gtu.getType());
590 Length leftSuitability = computeSuitabilityWithLaneChanges(currentLane, remainingDistance,
591 suitabilityOfLanesBeforeBranch, totalLanes, LateralDirectionality.LEFT, gtu.getType());
592 Length rightSuitability = computeSuitabilityWithLaneChanges(currentLane, remainingDistance,
593 suitabilityOfLanesBeforeBranch, totalLanes, LateralDirectionality.RIGHT, gtu.getType());
594 if (leftSuitability.ge(rightSuitability))
595 {
596 return leftSuitability;
597 }
598 else if (rightSuitability.ge(leftSuitability))
599 {
600
601 return rightSuitability;
602 }
603 if (leftSuitability.le(GETOFFTHISLANENOW))
604 {
605 throw new NetworkException("Changing lanes in any direction does not get the GTU on a suitable lane");
606 }
607 return leftSuitability;
608 }
609
610
611
612
613
614
615
616
617
618
619
620
621
622 protected final Length computeSuitabilityWithLaneChanges(final Lane startLane, final double remainingDistance,
623 final Map<Lane, Length> suitabilities, final int totalLanes, final LateralDirectionality direction,
624 final GtuType gtuType)
625 {
626
627
628
629
630
631
632
633
634
635 int laneChangesUsed = 0;
636 Lane currentLane = startLane;
637 Length currentSuitability = null;
638 while (null == currentSuitability)
639 {
640 laneChangesUsed++;
641 if (currentLane.accessibleAdjacentLanesLegal(direction, gtuType).size() == 0)
642 {
643 return GETOFFTHISLANENOW;
644 }
645 currentLane = currentLane.accessibleAdjacentLanesLegal(direction, gtuType).iterator().next();
646 currentSuitability = suitabilities.get(currentLane);
647 }
648 double fraction = currentSuitability == NOLANECHANGENEEDED ? 0 : 0.5;
649 int notSuitableLaneCount = totalLanes - suitabilities.size();
650 return new Length(
651 remainingDistance * (notSuitableLaneCount - laneChangesUsed + 1 + fraction) / (notSuitableLaneCount + fraction),
652 LengthUnit.SI);
653 }
654
655
656
657
658
659
660
661
662 protected final int countCompatibleLanes(final CrossSectionLink link, final GtuType gtuType)
663 {
664 int result = 0;
665 for (CrossSectionElement cse : link.getCrossSectionElementList())
666 {
667 if (cse instanceof Lane)
668 {
669 Lane l = (Lane) cse;
670 if (l.getType().isCompatible(gtuType))
671 {
672 result++;
673 }
674 }
675 }
676 return result;
677 }
678
679
680 @Override
681 public final String toString()
682 {
683 return "LaneBasedCFLCTacticalPlanner [laneChangeModel=" + this.laneChangeModel + "]";
684 }
685
686 }