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.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   * <p>
30   * Copyright (c) 2013-2019 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 OTSNetwork; network
56       * @param gtuType GTUType; gtu type
57       * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
58       * @param widthGenerator 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)
63              throws OTSGeometryException
64      {
65          buildConflicts(network, gtuType, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
66      }
67  
68      /**
69       * Build conflicts on network.
70       * @param network OTSNetwork; network
71       * @param gtuType GTUType; gtu type
72       * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
73       * @param widthGenerator WidthGenerator; width generator
74       * @param ignoreList LaneCombinationList; lane combinations to ignore
75       * @param permittedList LaneCombinationList; lane combinations that are permitted by traffic control
76       * @throws OTSGeometryException in case of geometry exception
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          // Create list of lanes
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      * Build conflicts on list of lanes.
104      * @param lanes List&lt;Lane&gt;; lanes
105      * @param gtuType GTUType; gtu type
106      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
107      * @param widthGenerator WidthGenerator; width generator
108      * @throws OTSGeometryException in case of geometry exception
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      * Build conflicts on list of lanes.
119      * @param lanes List&lt;Lane&gt;; list of Lanes
120      * @param gtuType GTUType; the GTU type
121      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; the simulator
122      * @param widthGenerator WidthGenerator; the width generator
123      * @param ignoreList LaneCombinationList; lane combinations to ignore
124      * @param permittedList LaneCombinationList; lane combinations that are permitted by traffic control
125      * @throws OTSGeometryException in case of geometry exception
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         // Loop Lane / GTUDirectionality combinations
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                         // See if conflict needs to be build, and build if so
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      * Build conflict on single lane pair. Connecting lanes are determined.
171      * @param lane1 Lane; lane 1
172      * @param dir1 GTUDirectionality; gtu direction 1
173      * @param lane2 Lane; lane 2
174      * @param dir2 GTUDirectionality; gtu direction 2
175      * @param gtuType GTUType; gtu type
176      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
177      * @param widthGenerator WidthGenerator; width generator
178      * @throws OTSGeometryException in case of geometry exception
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      * Build conflict on single lane pair. Connecting lanes are determined.
190      * @param lane1 Lane; lane 1
191      * @param dir1 GTUDirectionality; gtu direction 1
192      * @param lane2 Lane; lane 2
193      * @param dir2 GTUDirectionality; gtu direction 2
194      * @param gtuType GTUType; gtu type
195      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
196      * @param widthGenerator WidthGenerator; width generator
197      * @param permitted boolean; conflict permitted by traffic control
198      * @throws OTSGeometryException in case of geometry exception
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      * Build conflicts on single lane pair.
221      * @param lane1 Lane; lane 1
222      * @param dir1 GTUDirectionality; gtu direction 1
223      * @param down1 Map&lt;Lane,GTUDirectionality&gt;; downstream lanes 1
224      * @param up1 Map&lt;Lane,GTUDirectionality&gt;; upstream lanes 1
225      * @param lane2 Lane; lane 2
226      * @param dir2 GTUDirectionality; gtu direction 2
227      * @param down2 Map&lt;Lane,GTUDirectionality&gt;; downstream lane 2
228      * @param up2 Map&lt;Lane,GTUDirectionality&gt;; upstream lanes 2
229      * @param gtuType GTUType; gtu type
230      * @param permitted boolean; conflict permitted by traffic control
231      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
232      * @param widthGenerator WidthGenerator; width generator
233      * @throws OTSGeometryException in case of geometry exception
234      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
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         // Quick contour check, skip if not overlapping
245         if (!lane1.getContour().intersects(lane2.getContour()))
246         {
247             return;
248         }
249 
250         // Get left and right lines at specified width
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         // Get list of all intersection fractions
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         // Create merge
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                     // Same downstream lane, so a merge
277                     double fraction1 = Double.NaN;
278                     double fraction2 = Double.NaN;
279                     for (Intersection intersection : intersections)
280                     {
281                         // Only consider left/right and right/left intersections (others may or may not be at the end)
282                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
283                         {
284                             fraction1 = intersection.getFraction1();
285                             fraction2 = intersection.getFraction2();
286                         }
287                     }
288                     // Remove all intersections beyond this point, these are the result of line starts/ends matching
289                     Iterator<Intersection> iterator = intersections.iterator();
290                     while (iterator.hasNext())
291                     {
292                         if (iterator.next().getFraction1() >= fraction1)
293                         {
294                             iterator.remove();
295                         }
296                     }
297                     // Build conflict
298                     buildMergeConflict(lane1, dir1, fraction1, lane2, dir2, fraction2, gtuType, simulator, widthGenerator,
299                             permitted);
300                     // Skip loop for efficiency, and do not create multiple merges in case of multiple same downstream lanes
301                     merge = true;
302                 }
303             }
304         }
305 
306         // Create split
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                     // Same upstream lane, so a split
319                     double fraction1 = Double.NaN;
320                     double fraction2 = Double.NaN;
321                     for (Intersection intersection : intersections)
322                     {
323                         // Only consider left/right and right/left intersections (others may or may not be at the start)
324                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
325                         {
326                             fraction1 = intersection.getFraction1();
327                             fraction2 = intersection.getFraction2();
328                             break; // Split so first, not last
329                         }
330                     }
331                     // Remove all intersections up to this point, these are the result of line starts/ends matching
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                             // May skip further fraction
342                             break;
343                         }
344                     }
345                     // Build conflict
346                     buildSplitConflict(lane1, dir1, fraction1, lane2, dir2, fraction2, gtuType, simulator, widthGenerator);
347                     // Skip loop for efficiency, and do not create multiple splits in case of multiple same upstream lanes
348                     split = true;
349                 }
350             }
351         }
352 
353         // Create crossings
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             // First fraction found is start of conflict
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             // Flip crossed state of intersecting line combination
370             crossed[intersection.getCombo()] = !crossed[intersection.getCombo()];
371             // If all crossed or all not crossed, end of conflict
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      * Build a merge conflict.
393      * @param lane1 Lane; lane 1
394      * @param dir1 GTUDirectionality; gtu direction 1
395      * @param f1start double; start fraction 1
396      * @param lane2 Lane; lane 2
397      * @param dir2 GTUDirectionality; gtu direction 2
398      * @param f2start double; start fraction 2
399      * @param gtuType GTUType; gtu type
400      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
401      * @param widthGenerator WidthGenerator; width generator
402      * @param permitted boolean; conflict permitted by traffic control
403      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
404      * @throws OTSGeometryException in case of geometry exception
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         // Determine lane end from direction
414         double f1end = dir1.isPlus() ? 1.0 : 0.0;
415         double f2end = dir2.isPlus() ? 1.0 : 0.0;
416 
417         // Get locations and length
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         // Get geometries
424         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
425         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
426 
427         // Determine conflict rule
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         // Make conflict
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      * Build a split conflict.
447      * @param lane1 Lane; lane 1
448      * @param dir1 GTUDirectionality; gtu direction 1
449      * @param f1end double; end fraction 1
450      * @param lane2 Lane; lane 2
451      * @param dir2 GTUDirectionality; gtu direction 2
452      * @param f2end double; end fraction 2
453      * @param gtuType GTUType; gtu type
454      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
455      * @param widthGenerator WidthGenerator; width generator
456      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
457      * @throws OTSGeometryException in case of geometry exception
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         // Determine lane start from direction
467         double f1start = dir1.isPlus() ? 0.0 : 1.0;
468         double f2start = dir2.isPlus() ? 0.0 : 1.0;
469 
470         // Get locations and length
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         // Get geometries
477         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
478         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
479 
480         // Make conflict
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      * Build a crossing conflict.
487      * @param lane1 Lane; lane 1
488      * @param dir1 GTUDirectionality; gtu direction 1
489      * @param f1start double; start fraction 1
490      * @param f1end double; end fraction 1
491      * @param lane2 Lane; lane 2
492      * @param dir2 GTUDirectionality; gtu direction 2
493      * @param f2start double; start fraction 2
494      * @param f2end double; end fraction 2
495      * @param gtuType GTUType; gtu type
496      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
497      * @param widthGenerator WidthGenerator; width generator
498      * @param permitted boolean; conflict permitted by traffic control
499      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
500      * @throws OTSGeometryException in case of geometry exception
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         // Fractions may be in opposite direction, for the start location this needs to be correct
510         // Note: for geometry (real order, not considering direction) and length (absolute value) this does not matter
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         // Get locations and length
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         // Get geometries
537         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
538         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
539 
540         // Determine conflict rule
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         // Make conflict
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      * Creates geometry for conflict.
560      * @param lane Lane; lane
561      * @param fStart double; longitudinal fraction of start
562      * @param fEnd double; longitudinal fraction of end
563      * @param widthGenerator WidthGenerator; width generator
564      * @return geometry for conflict
565      * @throws OTSGeometryException in case of geometry exception
566      */
567     private static OTSLine3D getGeometry(final Lane lane, final double fStart, final double fEnd,
568             final WidthGenerator widthGenerator) throws OTSGeometryException
569     {
570         // extractFractional needs ordered fractions, irrespective of driving direction
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      * Intersection holds two fractions where two lines have crossed. There is also a combo to identify which lines have been
595      * used to find the intersection.
596      * <p>
597      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
598      * <br>
599      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
600      * <p>
601      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 21 dec. 2016 <br>
602      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
603      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
604      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
605      */
606     private static class Intersection implements Comparable<Intersection>
607     {
608 
609         /** Fraction on lane 1. */
610         private final double fraction1;
611 
612         /** Fraction on lane 2. */
613         private final double fraction2;
614 
615         /** Edge combination number. */
616         private final int combo;
617 
618         /**
619          * @param fraction1 double; fraction on lane 1
620          * @param fraction2 double; fraction on lane 1
621          * @param combo int; edge combination number
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          * @return fraction1.
632          */
633         public final double getFraction1()
634         {
635             return this.fraction1;
636         }
637 
638         /**
639          * @return fraction2.
640          */
641         public final double getFraction2()
642         {
643             return this.fraction2;
644         }
645 
646         /**
647          * @return combo.
648          */
649         public final int getCombo()
650         {
651             return this.combo;
652         }
653 
654         /** {@inheritDoc} */
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         /** {@inheritDoc} */
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         /** {@inheritDoc} */
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          * Returns a set of intersections, sorted by the fraction on line 1.
720          * @param line1 OTSLine3D; line 1
721          * @param line2 OTSLine3D; line 2
722          * @param combo int; edge combination number
723          * @return set of intersections, sorted by the fraction on line 1
724          * @throws OTSGeometryException in case of geometry exception
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                         // Segments intersect
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         /** {@inheritDoc} */
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      * Generator for width.
786      * <p>
787      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
788      * <br>
789      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
790      * <p>
791      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 dec. 2016 <br>
792      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
793      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
794      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
795      */
796     public interface WidthGenerator
797     {
798 
799         /**
800          * Returns the begin width of this lane.
801          * @param lane Lane; lane
802          * @param fraction double; fraction
803          * @return begin width of this lane
804          */
805         double getWidth(Lane lane, double fraction);
806 
807     }
808 
809     /**
810      * Generator with fixed width.
811      * <p>
812      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
813      * <br>
814      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
815      * <p>
816      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 dec. 2016 <br>
817      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
818      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
819      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
820      */
821     public static class FixedWidthGenerator implements WidthGenerator
822     {
823 
824         /** Fixed width. */
825         private final double width;
826 
827         /**
828          * Constructor with width.
829          * @param width Length; width
830          */
831         public FixedWidthGenerator(final Length width)
832         {
833             this.width = width.si;
834         }
835 
836         /** {@inheritDoc} */
837         @Override
838         public final double getWidth(final Lane lane, final double fraction)
839         {
840             return this.width;
841         }
842 
843         /** {@inheritDoc} */
844         @Override
845         public final String toString()
846         {
847             return "FixedWidthGenerator [width=" + this.width + "]";
848         }
849 
850     }
851 
852     /**
853      * Generator with width factor on actual lane width.
854      * <p>
855      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
856      * <br>
857      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
858      * <p>
859      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 dec. 2016 <br>
860      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
861      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
862      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
863      */
864     public static class RelativeWidthGenerator implements WidthGenerator
865     {
866 
867         /** Width factor. */
868         private final double factor;
869 
870         /**
871          * Constructor with width factor.
872          * @param factor double; width factor
873          */
874         public RelativeWidthGenerator(final double factor)
875         {
876             this.factor = factor;
877         }
878 
879         /** {@inheritDoc} */
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         /** {@inheritDoc} */
887         @Override
888         public final String toString()
889         {
890             return "RelativeWidthGenerator [factor=" + this.factor + "]";
891         }
892 
893     }
894 
895 }