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