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