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.road.network.OTSRoadNetwork;
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 OTSRoadNetwork; 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 OTSRoadNetwork 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 OTSRoadNetwork; 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 OTSRoadNetwork 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         if (!lane1.getParentLink().equals(lane2.getParentLink())) // tight inner-curves with dedicated Bezier ignored
355         {
356             boolean[] crossed = new boolean[4];
357             Iterator<Intersection> iterator = intersections.iterator();
358             double f1Start = Double.NaN;
359             double f2Start = Double.NaN;
360             double f2End = Double.NaN;
361             while (iterator.hasNext())
362             {
363                 Intersection intersection = iterator.next();
364                 // First fraction found is start of conflict
365                 if (Double.isNaN(f1Start))
366                 {
367                     f1Start = intersection.getFraction1();
368                 }
369                 f2Start = Double.isNaN(f2Start) ? intersection.getFraction2() : Math.min(f2Start, intersection.getFraction2());
370                 f2End = Double.isNaN(f2End) ? intersection.getFraction2() : Math.max(f2End, intersection.getFraction2());
371                 // Flip crossed state of intersecting line combination
372                 crossed[intersection.getCombo()] = !crossed[intersection.getCombo()];
373                 // If all crossed or all not crossed, end of conflict
374                 if ((crossed[0] && crossed[1] && crossed[2] && crossed[3])
375                         || (!crossed[0] && !crossed[1] && !crossed[2] && !crossed[3]))
376                 {
377                     if (dir2.isMinus())
378                     {
379                         double f2Temp = f2Start;
380                         f2Start = f2End;
381                         f2End = f2Temp;
382                     }
383                     buildCrossingConflict(lane1, dir1, f1Start, intersection.getFraction1(), lane2, dir2, f2Start, f2End,
384                             gtuType, simulator, widthGenerator, permitted);
385                     f1Start = Double.NaN;
386                     f2Start = Double.NaN;
387                     f2End = Double.NaN;
388                 }
389             }
390         }
391 
392     }
393 
394     /**
395      * Build a merge conflict.
396      * @param lane1 Lane; lane 1
397      * @param dir1 GTUDirectionality; gtu direction 1
398      * @param f1start double; start fraction 1
399      * @param lane2 Lane; lane 2
400      * @param dir2 GTUDirectionality; gtu direction 2
401      * @param f2start double; start fraction 2
402      * @param gtuType GTUType; gtu type
403      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
404      * @param widthGenerator WidthGenerator; width generator
405      * @param permitted boolean; conflict permitted by traffic control
406      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
407      * @throws OTSGeometryException in case of geometry exception
408      */
409     @SuppressWarnings("checkstyle:parameternumber")
410     private static void buildMergeConflict(final Lane lane1, final GTUDirectionality dir1, final double f1start,
411             final Lane lane2, final GTUDirectionality dir2, final double f2start, final GTUType gtuType,
412             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator, final boolean permitted)
413             throws NetworkException, OTSGeometryException
414     {
415 
416         // Determine lane end from direction
417         double f1end = dir1.isPlus() ? 1.0 : 0.0;
418         double f2end = dir2.isPlus() ? 1.0 : 0.0;
419 
420         // Get locations and length
421         Length longitudinalPosition1 = lane1.getLength().multiplyBy(f1start);
422         Length longitudinalPosition2 = lane2.getLength().multiplyBy(f2start);
423         Length length1 = lane1.getLength().multiplyBy(Math.abs(f1end - f1start));
424         Length length2 = lane2.getLength().multiplyBy(Math.abs(f2end - f2start));
425 
426         // Get geometries
427         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
428         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
429 
430         // Determine conflict rule
431         ConflictRule conflictRule;
432         if (lane1.getParentLink().getPriority().isBusStop() || lane2.getParentLink().getPriority().isBusStop())
433         {
434             Throw.when(lane1.getParentLink().getPriority().isBusStop() && lane2.getParentLink().getPriority().isBusStop(),
435                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
436             conflictRule = new BusStopConflictRule(simulator);
437         }
438         else
439         {
440             conflictRule = new DefaultConflictRule();
441         }
442 
443         // Make conflict
444         Conflict.generateConflictPair(ConflictType.MERGE, conflictRule, permitted, lane1, longitudinalPosition1, length1, dir1,
445                 geometry1, gtuType, lane2, longitudinalPosition2, length2, dir2, geometry2, gtuType, simulator);
446     }
447 
448     /**
449      * Build a split conflict.
450      * @param lane1 Lane; lane 1
451      * @param dir1 GTUDirectionality; gtu direction 1
452      * @param f1end double; end fraction 1
453      * @param lane2 Lane; lane 2
454      * @param dir2 GTUDirectionality; gtu direction 2
455      * @param f2end double; end fraction 2
456      * @param gtuType GTUType; gtu type
457      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
458      * @param widthGenerator WidthGenerator; width generator
459      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
460      * @throws OTSGeometryException in case of geometry exception
461      */
462     @SuppressWarnings("checkstyle:parameternumber")
463     private static void buildSplitConflict(final Lane lane1, final GTUDirectionality dir1, final double f1end, final Lane lane2,
464             final GTUDirectionality dir2, final double f2end, final GTUType gtuType,
465             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator)
466             throws NetworkException, OTSGeometryException
467     {
468 
469         // Determine lane start from direction
470         double f1start = dir1.isPlus() ? 0.0 : 1.0;
471         double f2start = dir2.isPlus() ? 0.0 : 1.0;
472 
473         // Get locations and length
474         Length longitudinalPosition1 = lane1.getLength().multiplyBy(f1start);
475         Length longitudinalPosition2 = lane2.getLength().multiplyBy(f2start);
476         Length length1 = lane1.getLength().multiplyBy(Math.abs(f1end - f1start));
477         Length length2 = lane2.getLength().multiplyBy(Math.abs(f2end - f2start));
478 
479         // Get geometries
480         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
481         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
482 
483         // Make conflict
484         Conflict.generateConflictPair(ConflictType.SPLIT, new SplitConflictRule(), false, lane1, longitudinalPosition1, length1,
485                 dir1, geometry1, gtuType, lane2, longitudinalPosition2, length2, dir2, geometry2, gtuType, simulator);
486     }
487 
488     /**
489      * Build a crossing conflict.
490      * @param lane1 Lane; lane 1
491      * @param dir1 GTUDirectionality; gtu direction 1
492      * @param f1start double; start fraction 1
493      * @param f1end double; end fraction 1
494      * @param lane2 Lane; lane 2
495      * @param dir2 GTUDirectionality; gtu direction 2
496      * @param f2start double; start fraction 2
497      * @param f2end double; end fraction 2
498      * @param gtuType GTUType; gtu type
499      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
500      * @param widthGenerator WidthGenerator; width generator
501      * @param permitted boolean; conflict permitted by traffic control
502      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
503      * @throws OTSGeometryException in case of geometry exception
504      */
505     @SuppressWarnings("checkstyle:parameternumber")
506     private static void buildCrossingConflict(final Lane lane1, final GTUDirectionality dir1, final double f1start,
507             final double f1end, final Lane lane2, final GTUDirectionality dir2, final double f2start, final double f2end,
508             final GTUType gtuType, final DEVSSimulatorInterface.TimeDoubleUnit simulator, final WidthGenerator widthGenerator,
509             final boolean permitted) throws NetworkException, OTSGeometryException
510     {
511 
512         // Fractions may be in opposite direction, for the start location this needs to be correct
513         // Note: for geometry (real order, not considering direction) and length (absolute value) this does not matter
514         double f1startDirected;
515         double f2startDirected;
516         if ((dir1.isPlus() && f1end < f1start) || (dir1.isMinus() && f1end > f1start))
517         {
518             f1startDirected = f1end;
519         }
520         else
521         {
522             f1startDirected = f1start;
523         }
524         if ((dir2.isPlus() && f2end < f2start) || (dir2.isMinus() && f2end > f2start))
525         {
526             f2startDirected = f2end;
527         }
528         else
529         {
530             f2startDirected = f2start;
531         }
532 
533         // Get locations and length
534         Length longitudinalPosition1 = lane1.getLength().multiplyBy(f1startDirected);
535         Length longitudinalPosition2 = lane2.getLength().multiplyBy(f2startDirected);
536         Length length1 = lane1.getLength().multiplyBy(Math.abs(f1end - f1start));
537         Length length2 = lane2.getLength().multiplyBy(Math.abs(f2end - f2start));
538 
539         // Get geometries
540         OTSLine3D geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
541         OTSLine3D geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
542 
543         // Determine conflict rule
544         ConflictRule conflictRule;
545         if (lane1.getParentLink().getPriority().isBusStop() || lane2.getParentLink().getPriority().isBusStop())
546         {
547             Throw.when(lane1.getParentLink().getPriority().isBusStop() && lane2.getParentLink().getPriority().isBusStop(),
548                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
549             conflictRule = new BusStopConflictRule(simulator);
550         }
551         else
552         {
553             conflictRule = new DefaultConflictRule();
554         }
555 
556         // Make conflict
557         Conflict.generateConflictPair(ConflictType.CROSSING, conflictRule, permitted, lane1, longitudinalPosition1, length1,
558                 dir1, geometry1, gtuType, lane2, longitudinalPosition2, length2, dir2, geometry2, gtuType, simulator);
559     }
560 
561     /**
562      * Creates geometry for conflict.
563      * @param lane Lane; lane
564      * @param fStart double; longitudinal fraction of start
565      * @param fEnd double; longitudinal fraction of end
566      * @param widthGenerator WidthGenerator; width generator
567      * @return geometry for conflict
568      * @throws OTSGeometryException in case of geometry exception
569      */
570     private static OTSLine3D getGeometry(final Lane lane, final double fStart, final double fEnd,
571             final WidthGenerator widthGenerator) throws OTSGeometryException
572     {
573         // extractFractional needs ordered fractions, irrespective of driving direction
574         double f1;
575         double f2;
576         if (fEnd > fStart)
577         {
578             f1 = fStart;
579             f2 = fEnd;
580         }
581         else
582         {
583             f1 = fEnd;
584             f2 = fStart;
585         }
586         OTSLine3D centerLine = lane.getCenterLine().extractFractional(f1, f2);
587         OTSLine3D left = centerLine.offsetLine(widthGenerator.getWidth(lane, f1) / 2, widthGenerator.getWidth(lane, f2) / 2);
588         OTSLine3D right =
589                 centerLine.offsetLine(-widthGenerator.getWidth(lane, f1) / 2, -widthGenerator.getWidth(lane, f2) / 2).reverse();
590         OTSPoint3D[] points = new OTSPoint3D[left.size() + right.size()];
591         System.arraycopy(left.getPoints(), 0, points, 0, left.size());
592         System.arraycopy(right.getPoints(), 0, points, left.size(), right.size());
593         return new OTSLine3D(points);
594     }
595 
596     /**
597      * Intersection holds two fractions where two lines have crossed. There is also a combo to identify which lines have been
598      * used to find the intersection.
599      * <p>
600      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
601      * <br>
602      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
603      * <p>
604      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 21 dec. 2016 <br>
605      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
606      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
607      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
608      */
609     private static class Intersection implements Comparable<Intersection>
610     {
611 
612         /** Fraction on lane 1. */
613         private final double fraction1;
614 
615         /** Fraction on lane 2. */
616         private final double fraction2;
617 
618         /** Edge combination number. */
619         private final int combo;
620 
621         /**
622          * @param fraction1 double; fraction on lane 1
623          * @param fraction2 double; fraction on lane 1
624          * @param combo int; edge combination number
625          */
626         Intersection(final double fraction1, final double fraction2, final int combo)
627         {
628             this.fraction1 = fraction1;
629             this.fraction2 = fraction2;
630             this.combo = combo;
631         }
632 
633         /**
634          * @return fraction1.
635          */
636         public final double getFraction1()
637         {
638             return this.fraction1;
639         }
640 
641         /**
642          * @return fraction2.
643          */
644         public final double getFraction2()
645         {
646             return this.fraction2;
647         }
648 
649         /**
650          * @return combo.
651          */
652         public final int getCombo()
653         {
654             return this.combo;
655         }
656 
657         /** {@inheritDoc} */
658         @Override
659         public int compareTo(final Intersection o)
660         {
661             int out = Double.compare(this.fraction1, o.fraction1);
662             if (out != 0)
663             {
664                 return out;
665             }
666             out = Double.compare(this.fraction2, o.fraction2);
667             if (out != 0)
668             {
669                 return out;
670             }
671             return Integer.compare(this.combo, o.combo);
672         }
673 
674         /** {@inheritDoc} */
675         @Override
676         public int hashCode()
677         {
678             final int prime = 31;
679             int result = 1;
680             result = prime * result + this.combo;
681             long temp;
682             temp = Double.doubleToLongBits(this.fraction1);
683             result = prime * result + (int) (temp ^ (temp >>> 32));
684             temp = Double.doubleToLongBits(this.fraction2);
685             result = prime * result + (int) (temp ^ (temp >>> 32));
686             return result;
687         }
688 
689         /** {@inheritDoc} */
690         @Override
691         public boolean equals(final Object obj)
692         {
693             if (this == obj)
694             {
695                 return true;
696             }
697             if (obj == null)
698             {
699                 return false;
700             }
701             if (getClass() != obj.getClass())
702             {
703                 return false;
704             }
705             Intersection other = (Intersection) obj;
706             if (this.combo != other.combo)
707             {
708                 return false;
709             }
710             if (Double.doubleToLongBits(this.fraction1) != Double.doubleToLongBits(other.fraction1))
711             {
712                 return false;
713             }
714             if (Double.doubleToLongBits(this.fraction2) != Double.doubleToLongBits(other.fraction2))
715             {
716                 return false;
717             }
718             return true;
719         }
720 
721         /**
722          * Returns a set of intersections, sorted by the fraction on line 1.
723          * @param line1 OTSLine3D; line 1
724          * @param line2 OTSLine3D; line 2
725          * @param combo int; edge combination number
726          * @return set of intersections, sorted by the fraction on line 1
727          * @throws OTSGeometryException in case of geometry exception
728          */
729         public static SortedSet<Intersection> getIntersectionList(final OTSLine3D line1, final OTSLine3D line2, final int combo)
730                 throws OTSGeometryException
731         {
732             SortedSet<Intersection> out = new TreeSet<>();
733 
734             double cumul1 = 0.0;
735             OTSPoint3D start1 = null;
736             OTSPoint3D end1 = line1.get(0);
737             for (int i = 0; i < line1.size() - 1; i++)
738             {
739                 start1 = end1;
740                 end1 = line1.get(i + 1);
741 
742                 double cumul2 = 0.0;
743                 OTSPoint3D start2 = null;
744                 OTSPoint3D end2 = line2.get(0);
745 
746                 for (int j = 0; j < line2.size() - 1; j++)
747                 {
748                     start2 = end2;
749                     end2 = line2.get(j + 1);
750 
751                     OTSPoint3D p = OTSPoint3D.intersectionOfLineSegments(start1, end1, start2, end2);
752                     if (p != null)
753                     {
754                         // Segments intersect
755                         double dx = p.x - start1.x;
756                         double dy = p.y - start1.y;
757                         double length1 = cumul1 + Math.sqrt(dx * dx + dy * dy);
758                         dx = p.x - start2.x;
759                         dy = p.y - start2.y;
760                         double length2 = cumul2 + Math.sqrt(dx * dx + dy * dy);
761                         out.add(new Intersection(length1 / line1.getLengthSI(), length2 / line2.getLengthSI(), combo));
762                     }
763 
764                     double dx = end2.x - start2.x;
765                     double dy = end2.y - start2.y;
766                     cumul2 += Math.sqrt(dx * dx + dy * dy);
767                 }
768 
769                 double dx = end1.x - start1.x;
770                 double dy = end1.y - start1.y;
771                 cumul1 += Math.sqrt(dx * dx + dy * dy);
772             }
773 
774             return out;
775         }
776 
777         /** {@inheritDoc} */
778         @Override
779         public String toString()
780         {
781             return "Intersection [fraction1=" + this.fraction1 + ", fraction2=" + this.fraction2 + ", combo=" + this.combo
782                     + "]";
783         }
784 
785     }
786 
787     /**
788      * Generator for width.
789      * <p>
790      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
791      * <br>
792      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
793      * <p>
794      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 dec. 2016 <br>
795      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
796      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
797      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
798      */
799     public interface WidthGenerator
800     {
801 
802         /**
803          * Returns the begin width of this lane.
804          * @param lane Lane; lane
805          * @param fraction double; fraction
806          * @return begin width of this lane
807          */
808         double getWidth(Lane lane, double fraction);
809 
810     }
811 
812     /**
813      * Generator with fixed width.
814      * <p>
815      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
816      * <br>
817      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
818      * <p>
819      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 dec. 2016 <br>
820      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
821      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
822      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
823      */
824     public static class FixedWidthGenerator implements WidthGenerator
825     {
826 
827         /** Fixed width. */
828         private final double width;
829 
830         /**
831          * Constructor with width.
832          * @param width Length; width
833          */
834         public FixedWidthGenerator(final Length width)
835         {
836             this.width = width.si;
837         }
838 
839         /** {@inheritDoc} */
840         @Override
841         public final double getWidth(final Lane lane, final double fraction)
842         {
843             return this.width;
844         }
845 
846         /** {@inheritDoc} */
847         @Override
848         public final String toString()
849         {
850             return "FixedWidthGenerator [width=" + this.width + "]";
851         }
852 
853     }
854 
855     /**
856      * Generator with width factor on actual lane width.
857      * <p>
858      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
859      * <br>
860      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
861      * <p>
862      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 dec. 2016 <br>
863      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
864      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
865      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
866      */
867     public static class RelativeWidthGenerator implements WidthGenerator
868     {
869 
870         /** Width factor. */
871         private final double factor;
872 
873         /**
874          * Constructor with width factor.
875          * @param factor double; width factor
876          */
877         public RelativeWidthGenerator(final double factor)
878         {
879             this.factor = factor;
880         }
881 
882         /** {@inheritDoc} */
883         @Override
884         public final double getWidth(final Lane lane, final double fraction)
885         {
886             return lane.getWidth(fraction).si * this.factor;
887         }
888 
889         /** {@inheritDoc} */
890         @Override
891         public final String toString()
892         {
893             return "RelativeWidthGenerator [factor=" + this.factor + "]";
894         }
895 
896     }
897 
898 }