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