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