1   package org.opentrafficsim.road.network.lane.conflict;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.Map.Entry;
8   import java.util.SortedSet;
9   import java.util.TreeSet;
10  
11  import org.djunits.value.vdouble.scalar.Length;
12  import org.djutils.exceptions.Throw;
13  import org.djutils.immutablecollections.ImmutableMap;
14  import org.opentrafficsim.core.geometry.OTSGeometryException;
15  import org.opentrafficsim.core.geometry.OTSLine3D;
16  import org.opentrafficsim.core.geometry.OTSPoint3D;
17  import org.opentrafficsim.core.gtu.GTUDirectionality;
18  import org.opentrafficsim.core.gtu.GTUType;
19  import org.opentrafficsim.core.network.Link;
20  import org.opentrafficsim.core.network.NetworkException;
21  import org.opentrafficsim.core.network.OTSNetwork;
22  import org.opentrafficsim.road.network.lane.CrossSectionElement;
23  import org.opentrafficsim.road.network.lane.CrossSectionLink;
24  import org.opentrafficsim.road.network.lane.Lane;
25  
26  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  public final class ConflictBuilder
40  {
41  
42      
43      public static final WidthGenerator DEFAULT_WIDTH_GENERATOR = new RelativeWidthGenerator(0.8);
44  
45      
46  
47  
48      private ConflictBuilder()
49      {
50          
51      }
52  
53      
54  
55  
56  
57  
58  
59  
60  
61      public static void buildConflicts(final OTSNetwork network, final GTUType gtuType,
62              final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator)
63              throws OTSGeometryException
64      {
65          buildConflicts(network, gtuType, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
66      }
67  
68      
69  
70  
71  
72  
73  
74  
75  
76  
77  
78      public static void buildConflicts(final OTSNetwork network, final GTUType gtuType,
79              final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator,
80              final LaneCombinationList ignoreList, final LaneCombinationList permittedList) throws OTSGeometryException
81      {
82          
83          ImmutableMap<String, Link> links = network.getLinkMap();
84          List<Lane> lanes = new ArrayList<>();
85          for (String linkId : links.keySet())
86          {
87              Link link = links.get(linkId);
88              if (link instanceof CrossSectionLink)
89              {
90                  for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList())
91                  {
92                      if (element instanceof Lane)
93                      {
94                          lanes.add((Lane) element);
95                      }
96                  }
97              }
98          }
99          buildConflicts(lanes, gtuType, simulator, widthGenerator, ignoreList, permittedList);
100     }
101 
102     
103 
104 
105 
106 
107 
108 
109 
110     public static void buildConflicts(final List<Lane> lanes, final GTUType gtuType,
111             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator)
112             throws OTSGeometryException
113     {
114         buildConflicts(lanes, gtuType, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
115     }
116 
117     
118 
119 
120 
121 
122 
123 
124 
125 
126 
127     public static void buildConflicts(final List<Lane> lanes, final GTUType gtuType,
128             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator,
129             final LaneCombinationList ignoreList, final LaneCombinationList permittedList) throws OTSGeometryException
130     {
131         
132         for (int i = 0; i < lanes.size(); i++)
133         {
134             Lane lane1 = lanes.get(i);
135             for (GTUDirectionality dir1 : lane1.getLaneType().getDirectionality(gtuType).getDirectionalities())
136             {
137                 Map<Lane, GTUDirectionality> down1 = lane1.downstreamLanes(dir1, gtuType);
138                 Map<Lane, GTUDirectionality> up1 = lane1.upstreamLanes(dir1, gtuType);
139 
140                 for (int j = i + 1; j < lanes.size(); j++)
141                 {
142                     Lane lane2 = lanes.get(j);
143                     if (ignoreList.contains(lane1, lane2))
144                     {
145                         continue;
146                     }
147                     boolean permitted = permittedList.contains(lane1, lane2);
148 
149                     for (GTUDirectionality dir2 : lane2.getLaneType().getDirectionality(gtuType).getDirectionalities())
150                     {
151                         Map<Lane, GTUDirectionality> down2 = lane2.downstreamLanes(dir2, gtuType);
152                         Map<Lane, GTUDirectionality> up2 = lane2.upstreamLanes(dir2, gtuType);
153                         
154                         try
155                         {
156                             buildConflicts(lane1, dir1, down1, up1, lane2, dir2, down2, up2, gtuType, permitted, simulator,
157                                     widthGenerator);
158                         }
159                         catch (NetworkException ne)
160                         {
161                             throw new RuntimeException("Conflict build with bad combination of types / rules.", ne);
162                         }
163                     }
164                 }
165             }
166         }
167     }
168 
169     
170 
171 
172 
173 
174 
175 
176 
177 
178 
179 
180     @SuppressWarnings("checkstyle:parameternumber")
181     public static void buildConflicts(final Lane lane1, final GTUDirectionality dir1, final Lane lane2,
182             final GTUDirectionality dir2, final GTUType gtuType, final DEVSSimulatorInterface.TimeDoubleUnit simulator,
183             final WidthGenerator widthGenerator) throws OTSGeometryException
184     {
185         buildConflicts(lane1, dir1, lane2, dir2, gtuType, simulator, widthGenerator, false);
186     }
187 
188     
189 
190 
191 
192 
193 
194 
195 
196 
197 
198 
199 
200     @SuppressWarnings("checkstyle:parameternumber")
201     public static void buildConflicts(final Lane lane1, final GTUDirectionality dir1, final Lane lane2,
202             final GTUDirectionality dir2, final GTUType gtuType, final DEVSSimulatorInterface.TimeDoubleUnit simulator,
203             final WidthGenerator widthGenerator, final boolean permitted) throws OTSGeometryException
204     {
205         Map<Lane, GTUDirectionality> down1 = lane1.downstreamLanes(dir1, gtuType);
206         Map<Lane, GTUDirectionality> up1 = lane1.upstreamLanes(dir1, gtuType);
207         Map<Lane, GTUDirectionality> down2 = lane2.downstreamLanes(dir2, gtuType);
208         Map<Lane, GTUDirectionality> up2 = lane2.upstreamLanes(dir2, gtuType);
209         try
210         {
211             buildConflicts(lane1, dir1, down1, up1, lane2, dir2, down2, up2, gtuType, permitted, simulator, widthGenerator);
212         }
213         catch (NetworkException ne)
214         {
215             throw new RuntimeException("Conflict build with bad combination of types / rules.", ne);
216         }
217     }
218 
219     
220 
221 
222 
223 
224 
225 
226 
227 
228 
229 
230 
231 
232 
233 
234 
235 
236     @SuppressWarnings("checkstyle:parameternumber")
237     private static void buildConflicts(final Lane lane1, final GTUDirectionality dir1, final Map<Lane, GTUDirectionality> down1,
238             final Map<Lane, GTUDirectionality> up1, final Lane lane2, final GTUDirectionality dir2,
239             final Map<Lane, GTUDirectionality> down2, final Map<Lane, GTUDirectionality> up2, final GTUType gtuType,
240             final boolean permitted, final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator)
241             throws OTSGeometryException, NetworkException
242     {
243 
244         
245         if (!lane1.getContour().intersects(lane2.getContour()))
246         {
247             return;
248         }
249 
250         
251         OTSLine3D line1 = lane1.getCenterLine();
252         OTSLine3D line2 = lane2.getCenterLine();
253         OTSLine3D left1 = line1.offsetLine(widthGenerator.getWidth(lane1, 0.0) / 2, widthGenerator.getWidth(lane1, 1.0) / 2);
254         OTSLine3D right1 = line1.offsetLine(-widthGenerator.getWidth(lane1, 0.0) / 2, -widthGenerator.getWidth(lane1, 1.0) / 2);
255         OTSLine3D left2 = line2.offsetLine(widthGenerator.getWidth(lane2, 0.0) / 2, widthGenerator.getWidth(lane2, 1.0) / 2);
256         OTSLine3D right2 = line2.offsetLine(-widthGenerator.getWidth(lane2, 0.0) / 2, -widthGenerator.getWidth(lane2, 1.0) / 2);
257 
258         
259         SortedSet<Intersection> intersections = Intersection.getIntersectionList(left1, left2, 0);
260         intersections.addAll(Intersection.getIntersectionList(left1, right2, 1));
261         intersections.addAll(Intersection.getIntersectionList(right1, left2, 2));
262         intersections.addAll(Intersection.getIntersectionList(right1, right2, 3));
263 
264         
265         Iterator<Entry<Lane, GTUDirectionality>> iterator1 = down1.entrySet().iterator();
266         Iterator<Entry<Lane, GTUDirectionality>> iterator2 = down2.entrySet().iterator();
267         boolean merge = false;
268         while (iterator1.hasNext() && !merge)
269         {
270             Entry<Lane, GTUDirectionality> next1 = iterator1.next();
271             while (iterator2.hasNext() && !merge)
272             {
273                 Entry<Lane, GTUDirectionality> next2 = iterator2.next();
274                 if (next1.equals(next2))
275                 {
276                     
277                     double fraction1 = Double.NaN;
278                     double fraction2 = Double.NaN;
279                     for (Intersection intersection : intersections)
280                     {
281                         
282                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
283                         {
284                             fraction1 = intersection.getFraction1();
285                             fraction2 = intersection.getFraction2();
286                         }
287                     }
288                     
289                     Iterator<Intersection> iterator = intersections.iterator();
290                     while (iterator.hasNext())
291                     {
292                         if (iterator.next().getFraction1() >= fraction1)
293                         {
294                             iterator.remove();
295                         }
296                     }
297                     
298                     buildMergeConflict(lane1, dir1, fraction1, lane2, dir2, fraction2, gtuType, simulator, widthGenerator,
299                             permitted);
300                     
301                     merge = true;
302                 }
303             }
304         }
305 
306         
307         iterator1 = up1.entrySet().iterator();
308         iterator2 = up2.entrySet().iterator();
309         boolean split = false;
310         while (iterator1.hasNext() && !split)
311         {
312             Entry<Lane, GTUDirectionality> prev1 = iterator1.next();
313             while (iterator2.hasNext() && !split)
314             {
315                 Entry<Lane, GTUDirectionality> prev2 = iterator2.next();
316                 if (prev1.equals(prev2))
317                 {
318                     
319                     double fraction1 = Double.NaN;
320                     double fraction2 = Double.NaN;
321                     for (Intersection intersection : intersections)
322                     {
323                         
324                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
325                         {
326                             fraction1 = intersection.getFraction1();
327                             fraction2 = intersection.getFraction2();
328                             break; 
329                         }
330                     }
331                     
332                     Iterator<Intersection> iterator = intersections.iterator();
333                     while (iterator.hasNext())
334                     {
335                         if (iterator.next().getFraction1() <= fraction1)
336                         {
337                             iterator.remove();
338                         }
339                         else
340                         {
341                             
342                             break;
343                         }
344                     }
345                     
346                     buildSplitConflict(lane1, dir1, fraction1, lane2, dir2, fraction2, gtuType, simulator, widthGenerator);
347                     
348                     split = true;
349                 }
350             }
351         }
352 
353         
354         boolean[] crossed = new boolean[4];
355         Iterator<Intersection> iterator = intersections.iterator();
356         double f1Start = Double.NaN;
357         double f2Start = Double.NaN;
358         double f2End = Double.NaN;
359         while (iterator.hasNext())
360         {
361             Intersection intersection = iterator.next();
362             
363             if (Double.isNaN(f1Start))
364             {
365                 f1Start = intersection.getFraction1();
366             }
367             f2Start = Double.isNaN(f2Start) ? intersection.getFraction2() : Math.min(f2Start, intersection.getFraction2());
368             f2End = Double.isNaN(f2End) ? intersection.getFraction2() : Math.max(f2End, intersection.getFraction2());
369             
370             crossed[intersection.getCombo()] = !crossed[intersection.getCombo()];
371             
372             if ((crossed[0] && crossed[1] && crossed[2] && crossed[3])
373                     || (!crossed[0] && !crossed[1] && !crossed[2] && !crossed[3]))
374             {
375                 if (dir2.isMinus())
376                 {
377                     double f2Temp = f2Start;
378                     f2Start = f2End;
379                     f2End = f2Temp;
380                 }
381                 buildCrossingConflict(lane1, dir1, f1Start, intersection.getFraction1(), lane2, dir2, f2Start, f2End, gtuType,
382                         simulator, widthGenerator, permitted);
383                 f1Start = Double.NaN;
384                 f2Start = Double.NaN;
385                 f2End = Double.NaN;
386             }
387         }
388 
389     }
390 
391     
392 
393 
394 
395 
396 
397 
398 
399 
400 
401 
402 
403 
404 
405 
406     @SuppressWarnings("checkstyle:parameternumber")
407     private static void buildMergeConflict(final Lane lane1, final GTUDirectionality dir1, final double f1start,
408             final Lane lane2, final GTUDirectionality dir2, final double f2start, final GTUType gtuType,
409             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator, final boolean permitted)
410             throws NetworkException, OTSGeometryException
411     {
412 
413         
414         double f1end = dir1.isPlus() ? 1.0 : 0.0;
415         double f2end = dir2.isPlus() ? 1.0 : 0.0;
416 
417         
418         Length longitudinalPosition1 = lane1.getLength().multiplyBy(f1start);
419         Length longitudinalPosition2 = lane2.getLength().multiplyBy(f2start);
420         Length length1 = lane1.getLength().multiplyBy(Math.abs(f1end - f1start));
421         Length length2 = lane2.getLength().multiplyBy(Math.abs(f2end - f2start));
422 
423         
424         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
425         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
426 
427         
428         ConflictRule conflictRule;
429         if (lane1.getParentLink().getPriority().isBusStop() || lane2.getParentLink().getPriority().isBusStop())
430         {
431             Throw.when(lane1.getParentLink().getPriority().isBusStop() && lane2.getParentLink().getPriority().isBusStop(),
432                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
433             conflictRule = new BusStopConflictRule(simulator);
434         }
435         else
436         {
437             conflictRule = new DefaultConflictRule();
438         }
439 
440         
441         Conflict.generateConflictPair(ConflictType.MERGE, conflictRule, permitted, lane1, longitudinalPosition1, length1, dir1,
442                 geometry1, gtuType, lane2, longitudinalPosition2, length2, dir2, geometry2, gtuType, simulator);
443     }
444 
445     
446 
447 
448 
449 
450 
451 
452 
453 
454 
455 
456 
457 
458 
459     @SuppressWarnings("checkstyle:parameternumber")
460     private static void buildSplitConflict(final Lane lane1, final GTUDirectionality dir1, final double f1end, final Lane lane2,
461             final GTUDirectionality dir2, final double f2end, final GTUType gtuType,
462             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator)
463             throws NetworkException, OTSGeometryException
464     {
465 
466         
467         double f1start = dir1.isPlus() ? 0.0 : 1.0;
468         double f2start = dir2.isPlus() ? 0.0 : 1.0;
469 
470         
471         Length longitudinalPosition1 = lane1.getLength().multiplyBy(f1start);
472         Length longitudinalPosition2 = lane2.getLength().multiplyBy(f2start);
473         Length length1 = lane1.getLength().multiplyBy(Math.abs(f1end - f1start));
474         Length length2 = lane2.getLength().multiplyBy(Math.abs(f2end - f2start));
475 
476         
477         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
478         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
479 
480         
481         Conflict.generateConflictPair(ConflictType.SPLIT, new SplitConflictRule(), false, lane1, longitudinalPosition1, length1,
482                 dir1, geometry1, gtuType, lane2, longitudinalPosition2, length2, dir2, geometry2, gtuType, simulator);
483     }
484 
485     
486 
487 
488 
489 
490 
491 
492 
493 
494 
495 
496 
497 
498 
499 
500 
501 
502     @SuppressWarnings("checkstyle:parameternumber")
503     private static void buildCrossingConflict(final Lane lane1, final GTUDirectionality dir1, final double f1start,
504             final double f1end, final Lane lane2, final GTUDirectionality dir2, final double f2start, final double f2end,
505             final GTUType gtuType, final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator,
506             final boolean permitted) throws NetworkException, OTSGeometryException
507     {
508 
509         
510         
511         double f1startDirected;
512         double f2startDirected;
513         if ((dir1.isPlus() && f1end < f1start) || (dir1.isMinus() && f1end > f1start))
514         {
515             f1startDirected = f1end;
516         }
517         else
518         {
519             f1startDirected = f1start;
520         }
521         if ((dir2.isPlus() && f2end < f2start) || (dir2.isMinus() && f2end > f2start))
522         {
523             f2startDirected = f2end;
524         }
525         else
526         {
527             f2startDirected = f2start;
528         }
529 
530         
531         Length longitudinalPosition1 = lane1.getLength().multiplyBy(f1startDirected);
532         Length longitudinalPosition2 = lane2.getLength().multiplyBy(f2startDirected);
533         Length length1 = lane1.getLength().multiplyBy(Math.abs(f1end - f1start));
534         Length length2 = lane2.getLength().multiplyBy(Math.abs(f2end - f2start));
535 
536         
537         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
538         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
539 
540         
541         ConflictRule conflictRule;
542         if (lane1.getParentLink().getPriority().isBusStop() || lane2.getParentLink().getPriority().isBusStop())
543         {
544             Throw.when(lane1.getParentLink().getPriority().isBusStop() && lane2.getParentLink().getPriority().isBusStop(),
545                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
546             conflictRule = new BusStopConflictRule(simulator);
547         }
548         else
549         {
550             conflictRule = new DefaultConflictRule();
551         }
552 
553         
554         Conflict.generateConflictPair(ConflictType.CROSSING, conflictRule, permitted, lane1, longitudinalPosition1, length1,
555                 dir1, geometry1, gtuType, lane2, longitudinalPosition2, length2, dir2, geometry2, gtuType, simulator);
556     }
557 
558     
559 
560 
561 
562 
563 
564 
565 
566 
567     private static OTSLine3D getGeometry(final Lane lane, final double fStart, final double fEnd,
568             final WidthGenerator widthGenerator) throws OTSGeometryException
569     {
570         
571         double f1;
572         double f2;
573         if (fEnd > fStart)
574         {
575             f1 = fStart;
576             f2 = fEnd;
577         }
578         else
579         {
580             f1 = fEnd;
581             f2 = fStart;
582         }
583         OTSLine3D centerLine = lane.getCenterLine().extractFractional(f1, f2);
584         OTSLine3D left = centerLine.offsetLine(widthGenerator.getWidth(lane, f1) / 2, widthGenerator.getWidth(lane, f2) / 2);
585         OTSLine3D right =
586                 centerLine.offsetLine(-widthGenerator.getWidth(lane, f1) / 2, -widthGenerator.getWidth(lane, f2) / 2).reverse();
587         OTSPoint3D[] points = new OTSPoint3D[left.size() + right.size()];
588         System.arraycopy(left.getPoints(), 0, points, 0, left.size());
589         System.arraycopy(right.getPoints(), 0, points, left.size(), right.size());
590         return new OTSLine3D(points);
591     }
592 
593     
594 
595 
596 
597 
598 
599 
600 
601 
602 
603 
604 
605 
606     private static class Intersection implements Comparable<Intersection>
607     {
608 
609         
610         private final double fraction1;
611 
612         
613         private final double fraction2;
614 
615         
616         private final int combo;
617 
618         
619 
620 
621 
622 
623         Intersection(final double fraction1, final double fraction2, final int combo)
624         {
625             this.fraction1 = fraction1;
626             this.fraction2 = fraction2;
627             this.combo = combo;
628         }
629 
630         
631 
632 
633         public final double getFraction1()
634         {
635             return this.fraction1;
636         }
637 
638         
639 
640 
641         public final double getFraction2()
642         {
643             return this.fraction2;
644         }
645 
646         
647 
648 
649         public final int getCombo()
650         {
651             return this.combo;
652         }
653 
654         
655         @Override
656         public int compareTo(final Intersection o)
657         {
658             int out = Double.compare(this.fraction1, o.fraction1);
659             if (out != 0)
660             {
661                 return out;
662             }
663             out = Double.compare(this.fraction2, o.fraction2);
664             if (out != 0)
665             {
666                 return out;
667             }
668             return Integer.compare(this.combo, o.combo);
669         }
670 
671         
672         @Override
673         public int hashCode()
674         {
675             final int prime = 31;
676             int result = 1;
677             result = prime * result + this.combo;
678             long temp;
679             temp = Double.doubleToLongBits(this.fraction1);
680             result = prime * result + (int) (temp ^ (temp >>> 32));
681             temp = Double.doubleToLongBits(this.fraction2);
682             result = prime * result + (int) (temp ^ (temp >>> 32));
683             return result;
684         }
685 
686         
687         @Override
688         public boolean equals(final Object obj)
689         {
690             if (this == obj)
691             {
692                 return true;
693             }
694             if (obj == null)
695             {
696                 return false;
697             }
698             if (getClass() != obj.getClass())
699             {
700                 return false;
701             }
702             Intersection other = (Intersection) obj;
703             if (this.combo != other.combo)
704             {
705                 return false;
706             }
707             if (Double.doubleToLongBits(this.fraction1) != Double.doubleToLongBits(other.fraction1))
708             {
709                 return false;
710             }
711             if (Double.doubleToLongBits(this.fraction2) != Double.doubleToLongBits(other.fraction2))
712             {
713                 return false;
714             }
715             return true;
716         }
717 
718         
719 
720 
721 
722 
723 
724 
725 
726         public static SortedSet<Intersection> getIntersectionList(final OTSLine3D line1, final OTSLine3D line2, final int combo)
727                 throws OTSGeometryException
728         {
729             SortedSet<Intersection> out = new TreeSet<>();
730 
731             double cumul1 = 0.0;
732             OTSPoint3D start1 = null;
733             OTSPoint3D end1 = line1.get(0);
734             for (int i = 0; i < line1.size() - 1; i++)
735             {
736                 start1 = end1;
737                 end1 = line1.get(i + 1);
738 
739                 double cumul2 = 0.0;
740                 OTSPoint3D start2 = null;
741                 OTSPoint3D end2 = line2.get(0);
742 
743                 for (int j = 0; j < line2.size() - 1; j++)
744                 {
745                     start2 = end2;
746                     end2 = line2.get(j + 1);
747 
748                     OTSPoint3D p = OTSPoint3D.intersectionOfLineSegments(start1, end1, start2, end2);
749                     if (p != null)
750                     {
751                         
752                         double dx = p.x - start1.x;
753                         double dy = p.y - start1.y;
754                         double length1 = cumul1 + Math.sqrt(dx * dx + dy * dy);
755                         dx = p.x - start2.x;
756                         dy = p.y - start2.y;
757                         double length2 = cumul2 + Math.sqrt(dx * dx + dy * dy);
758                         out.add(new Intersection(length1 / line1.getLengthSI(), length2 / line2.getLengthSI(), combo));
759                     }
760 
761                     double dx = end2.x - start2.x;
762                     double dy = end2.y - start2.y;
763                     cumul2 += Math.sqrt(dx * dx + dy * dy);
764                 }
765 
766                 double dx = end1.x - start1.x;
767                 double dy = end1.y - start1.y;
768                 cumul1 += Math.sqrt(dx * dx + dy * dy);
769             }
770 
771             return out;
772         }
773 
774         
775         @Override
776         public String toString()
777         {
778             return "Intersection [fraction1=" + this.fraction1 + ", fraction2=" + this.fraction2 + ", combo=" + this.combo
779                     + "]";
780         }
781 
782     }
783 
784     
785 
786 
787 
788 
789 
790 
791 
792 
793 
794 
795 
796     public interface WidthGenerator
797     {
798 
799         
800 
801 
802 
803 
804 
805         double getWidth(Lane lane, double fraction);
806 
807     }
808 
809     
810 
811 
812 
813 
814 
815 
816 
817 
818 
819 
820 
821     public static class FixedWidthGenerator implements WidthGenerator
822     {
823 
824         
825         private final double width;
826 
827         
828 
829 
830 
831         public FixedWidthGenerator(final Length width)
832         {
833             this.width = width.si;
834         }
835 
836         
837         @Override
838         public final double getWidth(final Lane lane, final double fraction)
839         {
840             return this.width;
841         }
842 
843         
844         @Override
845         public final String toString()
846         {
847             return "FixedWidthGenerator [width=" + this.width + "]";
848         }
849 
850     }
851 
852     
853 
854 
855 
856 
857 
858 
859 
860 
861 
862 
863 
864     public static class RelativeWidthGenerator implements WidthGenerator
865     {
866 
867         
868         private final double factor;
869 
870         
871 
872 
873 
874         public RelativeWidthGenerator(final double factor)
875         {
876             this.factor = factor;
877         }
878 
879         
880         @Override
881         public final double getWidth(final Lane lane, final double fraction)
882         {
883             return lane.getWidth(fraction).si * this.factor;
884         }
885 
886         
887         @Override
888         public final String toString()
889         {
890             return "RelativeWidthGenerator [factor=" + this.factor + "]";
891         }
892 
893     }
894 
895 }