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