View Javadoc
1   package org.opentrafficsim.road.network.conflict;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.LinkedHashMap;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.Set;
9   import java.util.SortedSet;
10  import java.util.TreeSet;
11  import java.util.concurrent.Executors;
12  import java.util.concurrent.ThreadPoolExecutor;
13  import java.util.concurrent.atomic.AtomicInteger;
14  
15  import org.djunits.value.vdouble.scalar.Length;
16  import org.djutils.draw.line.Polygon2d;
17  import org.djutils.draw.point.Point2d;
18  import org.djutils.exceptions.Throw;
19  import org.djutils.immutablecollections.ImmutableMap;
20  import org.opentrafficsim.base.OtsRuntimeException;
21  import org.opentrafficsim.base.geometry.OtsLine2d;
22  import org.opentrafficsim.base.logger.Logger;
23  import org.opentrafficsim.core.definitions.DefaultsNl;
24  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
25  import org.opentrafficsim.core.network.Link;
26  import org.opentrafficsim.core.network.NetworkException;
27  import org.opentrafficsim.road.network.CrossSectionElement;
28  import org.opentrafficsim.road.network.CrossSectionLink;
29  import org.opentrafficsim.road.network.Lane;
30  import org.opentrafficsim.road.network.RoadNetwork;
31  import org.opentrafficsim.road.network.Shoulder;
32  
33  import ch.qos.logback.classic.Level;
34  
35  /**
36   * Conflict builder allows automatic generation of conflicts. This happens based on the geometry of lanes. Parallel execution
37   * allows this algorithm to run faster. There are two parallel implementations:
38   * <ul>
39   * <li>Small; between two lanes.</li>
40   * <li>Big; between one particular lane, and all lanes further in a list (i.e. similar to a triangular matrix procedure).</li>
41   * </ul>
42   * <p>
43   * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
44   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
45   * </p>
46   * @author Alexander Verbraeck
47   * @author Peter Knoppers
48   * @author Wouter Schakel
49   * @see <a href="https://opentrafficsim.org/manual/99-appendices/conflict-areas/">Generation of conflics</a>
50   */
51  // TODO use z-coordinate for intersections of lines
52  // TODO use remove big parallel type, and use fibers for small tasks.
53  public final class ConflictBuilder
54  {
55      /** number of merge onflicts. */
56      private static AtomicInteger numberMergeConflicts = new AtomicInteger(0);
57  
58      /** number of split onflicts. */
59      private static AtomicInteger numberSplitConflicts = new AtomicInteger(0);
60  
61      /** number of cross onflicts. */
62      private static AtomicInteger numberCrossConflicts = new AtomicInteger(0);
63  
64      /** Default width generator for conflicts which uses 80% of the lane width. */
65      public static final WidthGenerator DEFAULT_WIDTH_GENERATOR = new RelativeWidthGenerator(0.8);
66  
67      /**
68       * Empty constructor.
69       */
70      private ConflictBuilder()
71      {
72          //
73      }
74  
75      /**
76       * Build conflicts on network.
77       * @param network network
78       * @param simulator simulator
79       * @param widthGenerator width generator
80       */
81      public static void buildConflicts(final RoadNetwork network, final OtsSimulatorInterface simulator,
82              final WidthGenerator widthGenerator)
83      {
84          buildConflicts(network, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
85      }
86  
87      /**
88       * Build conflicts on network.
89       * @param network network
90       * @param simulator simulator
91       * @param widthGenerator width generator
92       * @param ignoreList lane combinations to ignore
93       * @param permittedList lane combinations that are permitted by traffic control
94       */
95      public static void buildConflicts(final RoadNetwork network, final OtsSimulatorInterface simulator,
96              final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
97      {
98          buildConflicts(getLanes(network), simulator, widthGenerator, ignoreList, permittedList, null);
99      }
100 
101     /**
102      * Returns all the lanes in the network.
103      * @param network network.
104      * @return list if all lanes.
105      */
106     private static List<Lane> getLanes(final RoadNetwork network)
107     {
108         ImmutableMap<String, Link> links = network.getLinkMap();
109         List<Lane> lanes = new ArrayList<>();
110         for (String linkId : links.keySet())
111         {
112             Link link = links.get(linkId);
113             if (link instanceof CrossSectionLink)
114             {
115                 for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList())
116                 {
117                     if (element instanceof Lane lane && !(element instanceof Shoulder))
118                     {
119                         lanes.add((Lane) element);
120                     }
121                 }
122             }
123         }
124         return lanes;
125     }
126 
127     /**
128      * Build conflicts on list of lanes.
129      * @param lanes lanes
130      * @param simulator simulator
131      * @param widthGenerator width generator
132      */
133     public static void buildConflicts(final List<Lane> lanes, final OtsSimulatorInterface simulator,
134             final WidthGenerator widthGenerator)
135     {
136         buildConflicts(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList(), null);
137     }
138 
139     /**
140      * Build conflicts on list of lanes.
141      * @param lanes list of Lanes
142      * @param simulator the simulator
143      * @param widthGenerator the width generator
144      * @param ignoreList lane combinations to ignore
145      * @param permittedList lane combinations that are permitted by traffic control
146      * @param conflictId identification of the conflict (null value permitted)
147      */
148     public static void buildConflicts(final List<Lane> lanes, final OtsSimulatorInterface simulator,
149             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList,
150             final String conflictId)
151     {
152         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
153         Logger.ots().trace("GENERATING CONFLICTS (NON-PARALLEL MODE). {} COMBINATIONS", totalCombinations);
154         long lastReported = 0;
155         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
156         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();
157 
158         for (int i = 0; i < lanes.size(); i++)
159         {
160             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i)) / 2;
161             if (combinationsDone / 100000000 > lastReported)
162             {
163                 Logger.ots()
164                         .debug(String.format(
165                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
166                                         + "conflicts, %d crossing conflicts)",
167                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
168                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
169                 lastReported = combinationsDone / 100000000;
170             }
171             Lane lane1 = lanes.get(i);
172             Set<Lane> down1 = lane1.nextLanes(null);
173             Set<Lane> up1 = lane1.prevLanes(null);
174 
175             for (int j = i + 1; j < lanes.size(); j++)
176             {
177                 Lane lane2 = lanes.get(j);
178                 if (ignoreList.contains(lane1, lane2))
179                 {
180                     continue;
181                 }
182                 boolean permitted = permittedList.contains(lane1, lane2);
183 
184                 Set<Lane> down2 = lane2.nextLanes(null);
185                 Set<Lane> up2 = lane2.prevLanes(null);
186                 // See if conflict needs to be build, and build if so
187                 try
188                 {
189                     buildConflicts(lane1, down1, up1, lane2, down2, up2, permitted, simulator, widthGenerator, leftEdges,
190                             rightEdges, true, conflictId);
191                 }
192                 catch (NetworkException ne)
193                 {
194                     throw new OtsRuntimeException("Conflict build with bad combination of types / rules.", ne);
195                 }
196             }
197         }
198         Logger.ots()
199                 .trace(String.format(
200                         "generating conflicts complete (generated %d merge conflicts, %d split "
201                                 + "conflicts, %d crossing conflicts)",
202                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
203     }
204 
205     /**
206      * Build conflict on single lane pair. Connecting lanes are determined.
207      * @param lane1 lane 1
208      * @param lane2 lane 2
209      * @param simulator simulator
210      * @param widthGenerator width generator
211      */
212     @SuppressWarnings("checkstyle:parameternumber")
213     public static void buildConflicts(final Lane lane1, final Lane lane2, final OtsSimulatorInterface simulator,
214             final WidthGenerator widthGenerator)
215     {
216         buildConflicts(lane1, lane2, simulator, widthGenerator, false);
217     }
218 
219     /**
220      * Build conflict on single lane pair. Connecting lanes are determined.
221      * @param lane1 lane 1
222      * @param lane2 lane 2
223      * @param simulator simulator
224      * @param widthGenerator width generator
225      * @param permitted conflict permitted by traffic control
226      */
227     @SuppressWarnings("checkstyle:parameternumber")
228     public static void buildConflicts(final Lane lane1, final Lane lane2, final OtsSimulatorInterface simulator,
229             final WidthGenerator widthGenerator, final boolean permitted)
230     {
231         Set<Lane> down1 = lane1.nextLanes(null);
232         Set<Lane> up1 = lane1.prevLanes(null);
233         Set<Lane> down2 = lane2.nextLanes(null);
234         Set<Lane> up2 = lane2.prevLanes(null);
235         try
236         {
237             buildConflicts(lane1, down1, up1, lane2, down2, up2, permitted, simulator, widthGenerator, new LinkedHashMap<>(),
238                     new LinkedHashMap<>(), true, null);
239         }
240         catch (NetworkException ne)
241         {
242             throw new OtsRuntimeException("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 down1 downstream lanes 1
250      * @param up1 upstream lanes 1
251      * @param lane2 lane 2
252      * @param down2 downstream lane 2
253      * @param up2 upstream lanes 2
254      * @param permitted conflict permitted by traffic control
255      * @param simulator simulator
256      * @param widthGenerator width generator
257      * @param leftEdges cache of left edge lines
258      * @param rightEdges cache of right edge lines
259      * @param intersectionCheck indicate whether we have to do a contour intersection check still
260      * @param conflictId identification of the conflict (may be null)
261      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
262      */
263     @SuppressWarnings({"checkstyle:parameternumber", "checkstyle:methodlength"})
264     static void buildConflicts(final Lane lane1, final Set<Lane> down1, final Set<Lane> up1, final Lane lane2,
265             final Set<Lane> down2, final Set<Lane> up2, final boolean permitted, final OtsSimulatorInterface simulator,
266             final WidthGenerator widthGenerator, final Map<Lane, OtsLine2d> leftEdges, final Map<Lane, OtsLine2d> rightEdges,
267             final boolean intersectionCheck, final String conflictId) throws NetworkException
268     {
269         // Quick contour check, skip if not overlapping -- Don't repeat if it has taken place
270         if (intersectionCheck)
271         {
272             if (!lane1.getAbsoluteContour().intersects(lane2.getAbsoluteContour()))
273             {
274                 return;
275             }
276         }
277 
278         // TODO: we cache, but the width generator may be different
279 
280         String paddedConflictId = null == conflictId ? "" : (" in conflict group " + conflictId);
281         // Get left and right lines at specified width
282         OtsLine2d left1;
283         OtsLine2d right1;
284         synchronized (lane1)
285         {
286             left1 = leftEdges.get(lane1);
287             right1 = rightEdges.get(lane1);
288             OtsLine2d line1 = lane1.getCenterLine();
289             if (null == left1)
290             {
291                 left1 = line1.offsetLine(widthGenerator.getWidth(lane1, 0.0) / 2, widthGenerator.getWidth(lane1, 1.0) / 2);
292                 leftEdges.put(lane1, left1);
293             }
294             if (null == right1)
295             {
296                 right1 = line1.offsetLine(-widthGenerator.getWidth(lane1, 0.0) / 2, -widthGenerator.getWidth(lane1, 1.0) / 2);
297                 rightEdges.put(lane1, right1);
298             }
299         }
300 
301         OtsLine2d left2;
302         OtsLine2d right2;
303         synchronized (lane2)
304         {
305             left2 = leftEdges.get(lane2);
306             right2 = rightEdges.get(lane2);
307             OtsLine2d line2 = lane2.getCenterLine();
308             if (null == left2)
309             {
310                 left2 = line2.offsetLine(widthGenerator.getWidth(lane2, 0.0) / 2, widthGenerator.getWidth(lane2, 1.0) / 2);
311                 leftEdges.put(lane2, left2);
312             }
313             if (null == right2)
314             {
315                 right2 = line2.offsetLine(-widthGenerator.getWidth(lane2, 0.0) / 2, -widthGenerator.getWidth(lane2, 1.0) / 2);
316                 rightEdges.put(lane2, right2);
317             }
318         }
319 
320         // Get list of all intersection fractions
321         SortedSet<Intersection> intersections = Intersection.getIntersectionList(left1, left2, 0);
322         intersections.addAll(Intersection.getIntersectionList(left1, right2, 1));
323         intersections.addAll(Intersection.getIntersectionList(right1, left2, 2));
324         intersections.addAll(Intersection.getIntersectionList(right1, right2, 3));
325 
326         // Create merge
327         Iterator<Lane> iterator1 = down1.iterator();
328         Iterator<Lane> iterator2 = down2.iterator();
329         boolean merge = false;
330         while (iterator1.hasNext() && !merge)
331         {
332             Lane next1 = iterator1.next();
333             while (iterator2.hasNext() && !merge)
334             {
335                 Lane next2 = iterator2.next();
336                 if (next1.equals(next2))
337                 {
338                     // Same downstream lane, so a merge
339                     double fraction1 = Double.NaN;
340                     double fraction2 = Double.NaN;
341                     for (Intersection intersection : intersections)
342                     {
343                         // Only consider left/right and right/left intersections (others may or may not be at the end)
344                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
345                         {
346                             fraction1 = intersection.getFraction1();
347                             fraction2 = intersection.getFraction2();
348                         }
349                     }
350                     // Remove all intersections beyond this point, these are the result of line starts/ends matching
351                     Iterator<Intersection> iterator = intersections.iterator();
352                     while (iterator.hasNext())
353                     {
354                         if (iterator.next().getFraction1() >= fraction1)
355                         {
356                             iterator.remove();
357                         }
358                     }
359                     if (Double.isNaN(fraction1))
360                     {
361                         Logger.ots().info("Fixing fractions of merge conflict between lane {} and {}", lane1.getFullId(),
362                                 lane2.getFullId());
363                         fraction1 = 0;
364                         fraction2 = 0;
365                     }
366                     // Build conflict
367                     buildMergeConflict(lane1, fraction1, lane2, fraction2, simulator, widthGenerator, permitted);
368                     // Skip loop for efficiency, and do not create multiple merges in case of multiple same downstream lanes
369                     merge = true;
370                 }
371             }
372         }
373 
374         // Create split
375         iterator1 = up1.iterator();
376         iterator2 = up2.iterator();
377         boolean split = false;
378         while (iterator1.hasNext() && !split)
379         {
380             Lane prev1 = iterator1.next();
381             while (iterator2.hasNext() && !split)
382             {
383                 Lane prev2 = iterator2.next();
384                 if (prev1.equals(prev2))
385                 {
386                     // Same upstream lane, so a split
387                     double fraction1 = Double.NaN;
388                     double fraction2 = Double.NaN;
389                     for (Intersection intersection : intersections)
390                     {
391                         // Only consider left/right and right/left intersections (others may or may not be at the start)
392                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
393                         {
394                             fraction1 = intersection.getFraction1();
395                             fraction2 = intersection.getFraction2();
396                             break; // Split so first, not last
397                         }
398                     }
399                     // Remove all intersections up to this point, these are the result of line starts/ends matching
400                     Iterator<Intersection> iterator = intersections.iterator();
401                     while (iterator.hasNext())
402                     {
403                         if (iterator.next().getFraction1() <= fraction1)
404                         {
405                             iterator.remove();
406                         }
407                         else
408                         {
409                             // May skip further fraction
410                             break;
411                         }
412                     }
413                     if (Double.isNaN(fraction1))
414                     {
415                         Logger.ots().info("Fixing fractions of split conflict{}", paddedConflictId);
416                         fraction1 = 1;
417                         fraction2 = 1;
418                     }
419                     // Build conflict
420                     buildSplitConflict(lane1, fraction1, lane2, fraction2, simulator, widthGenerator);
421                     // Skip loop for efficiency, and do not create multiple splits in case of multiple same upstream lanes
422                     split = true;
423                 }
424             }
425         }
426 
427         // Create crossings
428         if (!lane1.getLink().equals(lane2.getLink())) // tight inner-curves with dedicated Bezier ignored
429         {
430             boolean[] crossed = new boolean[4];
431             Iterator<Intersection> iterator = intersections.iterator();
432             double f1Start = Double.NaN;
433             double f2Start = Double.NaN;
434             double f2End = Double.NaN;
435             while (iterator.hasNext())
436             {
437                 Intersection intersection = iterator.next();
438                 // First fraction found is start of conflict
439                 if (Double.isNaN(f1Start))
440                 {
441                     f1Start = intersection.getFraction1();
442                 }
443                 f2Start = Double.isNaN(f2Start) ? intersection.getFraction2() : Math.min(f2Start, intersection.getFraction2());
444                 f2End = Double.isNaN(f2End) ? intersection.getFraction2() : Math.max(f2End, intersection.getFraction2());
445                 // Flip crossed state of intersecting line combination
446                 crossed[intersection.getCombo()] = !crossed[intersection.getCombo()];
447                 // If all crossed or all not crossed, end of conflict
448                 if ((crossed[0] && crossed[1] && crossed[2] && crossed[3])
449                         || (!crossed[0] && !crossed[1] && !crossed[2] && !crossed[3]))
450                 {
451                     if (Double.isNaN(f1Start) || Double.isNaN(f2Start) || Double.isNaN(f2End))
452                     {
453                         Logger.ots().warn("NOT YET Fixing fractions of crossing conflict{}", paddedConflictId);
454                     }
455                     buildCrossingConflict(lane1, f1Start, intersection.getFraction1(), lane2, f2Start, f2End, simulator,
456                             widthGenerator, permitted);
457                     f1Start = Double.NaN;
458                     f2Start = Double.NaN;
459                     f2End = Double.NaN;
460                 }
461             }
462         }
463 
464     }
465 
466     /**
467      * Build a merge conflict.
468      * @param lane1 lane 1
469      * @param f1start start fraction 1
470      * @param lane2 lane 2
471      * @param f2start start fraction 2
472      * @param simulator simulator
473      * @param widthGenerator width generator
474      * @param permitted conflict permitted by traffic control
475      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
476      */
477     @SuppressWarnings("checkstyle:parameternumber")
478     private static void buildMergeConflict(final Lane lane1, final double f1start, final Lane lane2, final double f2start,
479             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator, final boolean permitted)
480             throws NetworkException
481     {
482 
483         // Determine lane end from direction
484         double f1end = 1.0;
485         double f2end = 1.0;
486 
487         // Get locations and length
488         Length longitudinalPosition1 = lane1.getLength().times(f1start);
489         Length longitudinalPosition2 = lane2.getLength().times(f2start);
490         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
491         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));
492 
493         // Get geometries
494         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
495         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
496 
497         // Determine conflict rule
498         ConflictRule conflictRule;
499         if (lane1.getLink().getPriority().isBusStop() || lane2.getLink().getPriority().isBusStop())
500         {
501             Throw.when(lane1.getLink().getPriority().isBusStop() && lane2.getLink().getPriority().isBusStop(),
502                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
503             // TODO: handle bus priority on the model side
504             conflictRule = new BusStopConflictRule(simulator, DefaultsNl.BUS);
505         }
506         else
507         {
508             conflictRule = new DefaultConflictRule();
509         }
510 
511         // Make conflict
512         Conflict.generateConflictPair(ConflictType.MERGE, conflictRule, permitted, lane1, longitudinalPosition1, length1,
513                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);
514 
515         numberMergeConflicts.incrementAndGet();
516     }
517 
518     /**
519      * Build a split conflict.
520      * @param lane1 lane 1
521      * @param f1end end fraction 1
522      * @param lane2 lane 2
523      * @param f2end end fraction 2
524      * @param simulator simulator
525      * @param widthGenerator width generator
526      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
527      */
528     @SuppressWarnings("checkstyle:parameternumber")
529     private static void buildSplitConflict(final Lane lane1, final double f1end, final Lane lane2, final double f2end,
530             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator) throws NetworkException
531     {
532 
533         // Determine lane start from direction
534         double f1start = 0.0;
535         double f2start = 0.0;
536 
537         // Get locations and length
538         Length longitudinalPosition1 = lane1.getLength().times(f1start);
539         Length longitudinalPosition2 = lane2.getLength().times(f2start);
540         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
541         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));
542 
543         // Get geometries
544         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
545         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
546 
547         // Make conflict
548         Conflict.generateConflictPair(ConflictType.SPLIT, new SplitConflictRule(), false, lane1, longitudinalPosition1, length1,
549                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);
550 
551         numberSplitConflicts.incrementAndGet();
552     }
553 
554     /**
555      * Build a crossing conflict.
556      * @param lane1 lane 1
557      * @param f1start start fraction 1
558      * @param f1end end fraction 1
559      * @param lane2 lane 2
560      * @param f2start start fraction 2
561      * @param f2end end fraction 2
562      * @param simulator simulator
563      * @param widthGenerator width generator
564      * @param permitted conflict permitted by traffic control
565      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
566      */
567     @SuppressWarnings("checkstyle:parameternumber")
568     private static void buildCrossingConflict(final Lane lane1, final double f1start, final double f1end, final Lane lane2,
569             final double f2start, final double f2end, final OtsSimulatorInterface simulator,
570             final WidthGenerator widthGenerator, final boolean permitted) throws NetworkException
571     {
572 
573         // Fractions may be in opposite direction, for the start location this needs to be correct
574         // Note: for geometry (real order, not considering direction) and length (absolute value) this does not matter
575         double f1startDirected;
576         double f2startDirected;
577         if (f1end < f1start)
578         {
579             f1startDirected = f1end;
580         }
581         else
582         {
583             f1startDirected = f1start;
584         }
585         if (f2end < f2start)
586         {
587             f2startDirected = f2end;
588         }
589         else
590         {
591             f2startDirected = f2start;
592         }
593 
594         // Get locations and length
595         Length longitudinalPosition1 = lane1.getLength().times(f1startDirected);
596         Length longitudinalPosition2 = lane2.getLength().times(f2startDirected);
597         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
598         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));
599 
600         // Get geometries
601         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
602         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
603 
604         // Determine conflict rule
605         ConflictRule conflictRule;
606         if (lane1.getLink().getPriority().isBusStop() || lane2.getLink().getPriority().isBusStop())
607         {
608             Throw.when(lane1.getLink().getPriority().isBusStop() && lane2.getLink().getPriority().isBusStop(),
609                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
610             // TODO: handle bus priority on the model side
611             conflictRule = new BusStopConflictRule(simulator, DefaultsNl.BUS);
612         }
613         else
614         {
615             conflictRule = new DefaultConflictRule();
616         }
617 
618         // Make conflict
619         Conflict.generateConflictPair(ConflictType.CROSSING, conflictRule, permitted, lane1, longitudinalPosition1, length1,
620                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);
621 
622         numberCrossConflicts.incrementAndGet();
623     }
624 
625     /**
626      * Creates geometry for conflict.
627      * @param lane lane
628      * @param fStart longitudinal fraction of start
629      * @param fEnd longitudinal fraction of end
630      * @param widthGenerator width generator
631      * @return geometry for conflict
632      */
633     private static Polygon2d getGeometry(final Lane lane, final double fStart, final double fEnd,
634             final WidthGenerator widthGenerator)
635     {
636         // extractFractional needs ordered fractions, irrespective of driving direction
637         double f1;
638         double f2;
639         if (fEnd > fStart)
640         {
641             f1 = fStart;
642             f2 = fEnd;
643         }
644         else
645         {
646             f1 = fEnd;
647             f2 = fStart;
648         }
649         if (Math.abs(f1 - f2) < 1E-8)
650         {
651             Logger.ots().debug("f1 (" + f1 + ") equals f2 (" + f2 + "); problematic lane is " + lane.toString());
652             // Fix up
653             if (f1 > 0)
654             {
655                 f1 = f1 - f1 / 1000;
656             }
657             else
658             {
659                 f2 = f2 + f2 / 1000;
660             }
661         }
662         OtsLine2d centerLine = lane.getCenterLine().extractFractional(f1, f2);
663         OtsLine2d left = centerLine.offsetLine(widthGenerator.getWidth(lane, f1) / 2, widthGenerator.getWidth(lane, f2) / 2);
664         OtsLine2d right =
665                 centerLine.offsetLine(-widthGenerator.getWidth(lane, f1) / 2, -widthGenerator.getWidth(lane, f2) / 2).reverse();
666         List<Point2d> points = new ArrayList<>(left.size() + right.size());
667         points.addAll(left.getPointList());
668         points.addAll(right.getPointList());
669         return new Polygon2d(0.0, points);
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-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
677      * <br>
678      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
679      * </p>
680      * @author Alexander Verbraeck
681      * @author Peter Knoppers
682      * @author Wouter Schakel
683      */
684     private static class Intersection implements Comparable<Intersection>
685     {
686 
687         /** Fraction on lane 1. */
688         private final double fraction1;
689 
690         /** Fraction on lane 2. */
691         private final double fraction2;
692 
693         /** Edge combination number. */
694         private final int combo;
695 
696         /**
697          * @param fraction1 fraction on lane 1
698          * @param fraction2 fraction on lane 1
699          * @param combo edge combination number
700          */
701         Intersection(final double fraction1, final double fraction2, final int combo)
702         {
703             this.fraction1 = fraction1;
704             this.fraction2 = fraction2;
705             this.combo = combo;
706         }
707 
708         /**
709          * @return fraction1.
710          */
711         public final double getFraction1()
712         {
713             return this.fraction1;
714         }
715 
716         /**
717          * @return fraction2.
718          */
719         public final double getFraction2()
720         {
721             return this.fraction2;
722         }
723 
724         /**
725          * @return combo.
726          */
727         public final int getCombo()
728         {
729             return this.combo;
730         }
731 
732         @Override
733         public int compareTo(final Intersection o)
734         {
735             int out = Double.compare(this.fraction1, o.fraction1);
736             if (out != 0)
737             {
738                 return out;
739             }
740             out = Double.compare(this.fraction2, o.fraction2);
741             if (out != 0)
742             {
743                 return out;
744             }
745             return Integer.compare(this.combo, o.combo);
746         }
747 
748         @Override
749         public int hashCode()
750         {
751             final int prime = 31;
752             int result = 1;
753             result = prime * result + this.combo;
754             long temp;
755             temp = Double.doubleToLongBits(this.fraction1);
756             result = prime * result + (int) (temp ^ (temp >>> 32));
757             temp = Double.doubleToLongBits(this.fraction2);
758             result = prime * result + (int) (temp ^ (temp >>> 32));
759             return result;
760         }
761 
762         @Override
763         public boolean equals(final Object obj)
764         {
765             if (this == obj)
766             {
767                 return true;
768             }
769             if (obj == null)
770             {
771                 return false;
772             }
773             if (getClass() != obj.getClass())
774             {
775                 return false;
776             }
777             Intersection other = (Intersection) obj;
778             if (this.combo != other.combo)
779             {
780                 return false;
781             }
782             if (Double.doubleToLongBits(this.fraction1) != Double.doubleToLongBits(other.fraction1))
783             {
784                 return false;
785             }
786             if (Double.doubleToLongBits(this.fraction2) != Double.doubleToLongBits(other.fraction2))
787             {
788                 return false;
789             }
790             return true;
791         }
792 
793         /**
794          * Returns a set of intersections, sorted by the fraction on line 1.
795          * @param line1 line 1
796          * @param line2 line 2
797          * @param combo edge combination number
798          * @return set of intersections, sorted by the fraction on line 1
799          */
800         public static SortedSet<Intersection> getIntersectionList(final OtsLine2d line1, final OtsLine2d line2, final int combo)
801         {
802             SortedSet<Intersection> out = new TreeSet<>();
803             double cumul1 = 0.0;
804             Point2d start1 = null;
805             Point2d end1 = line1.get(0);
806             for (int i = 0; i < line1.size() - 1; i++)
807             {
808                 start1 = end1;
809                 end1 = line1.get(i + 1);
810 
811                 double cumul2 = 0.0;
812                 Point2d start2 = null;
813                 Point2d end2 = line2.get(0);
814 
815                 for (int j = 0; j < line2.size() - 1; j++)
816                 {
817                     start2 = end2;
818                     end2 = line2.get(j + 1);
819 
820                     Point2d p = Point2d.intersectionOfLineSegments(start1, end1, start2, end2);
821                     if (p != null)
822                     {
823                         // Segments intersect
824                         double dx = p.x - start1.x;
825                         double dy = p.y - start1.y;
826                         double length1 = cumul1 + Math.hypot(dx, dy);
827                         dx = p.x - start2.x;
828                         dy = p.y - start2.y;
829                         double length2 = cumul2 + Math.hypot(dx, dy);
830                         out.add(new Intersection(length1 / line1.getLength(), length2 / line2.getLength(), combo));
831                     }
832 
833                     double dx = end2.x - start2.x;
834                     double dy = end2.y - start2.y;
835                     cumul2 += Math.hypot(dx, dy);
836                 }
837 
838                 double dx = end1.x - start1.x;
839                 double dy = end1.y - start1.y;
840                 cumul1 += Math.hypot(dx, dy);
841             }
842 
843             return out;
844         }
845 
846         @Override
847         public String toString()
848         {
849             return "Intersection [fraction1=" + this.fraction1 + ", fraction2=" + this.fraction2 + ", combo=" + this.combo
850                     + "]";
851         }
852 
853     }
854 
855     /**
856      * Generator for width.
857      * <p>
858      * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
859      * <br>
860      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
861      * </p>
862      * @author Alexander Verbraeck
863      * @author Peter Knoppers
864      * @author Wouter Schakel
865      */
866     public interface WidthGenerator
867     {
868 
869         /**
870          * Returns the begin width of this lane.
871          * @param lane lane
872          * @param fraction fraction
873          * @return begin width of this lane
874          */
875         double getWidth(Lane lane, double fraction);
876 
877     }
878 
879     /**
880      * Generator with fixed width.
881      * <p>
882      * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
883      * <br>
884      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
885      * </p>
886      * @author Alexander Verbraeck
887      * @author Peter Knoppers
888      * @author Wouter Schakel
889      */
890     public static class FixedWidthGenerator implements WidthGenerator
891     {
892 
893         /** Fixed width. */
894         private final double width;
895 
896         /**
897          * Constructor with width.
898          * @param width width
899          */
900         public FixedWidthGenerator(final Length width)
901         {
902             this.width = width.si;
903         }
904 
905         @Override
906         public final double getWidth(final Lane lane, final double fraction)
907         {
908             return this.width;
909         }
910 
911         @Override
912         public final String toString()
913         {
914             return "FixedWidthGenerator [width=" + this.width + "]";
915         }
916 
917     }
918 
919     /**
920      * Generator with width factor on actual lane width.
921      * <p>
922      * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
923      * <br>
924      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
925      * </p>
926      * @author Alexander Verbraeck
927      * @author Peter Knoppers
928      * @author Wouter Schakel
929      */
930     public static class RelativeWidthGenerator implements WidthGenerator
931     {
932 
933         /** Width factor. */
934         private final double factor;
935 
936         /**
937          * Constructor with width factor.
938          * @param factor width factor
939          */
940         public RelativeWidthGenerator(final double factor)
941         {
942             this.factor = factor;
943         }
944 
945         @Override
946         public final double getWidth(final Lane lane, final double fraction)
947         {
948             return lane.getWidth(fraction).si * this.factor;
949         }
950 
951         @Override
952         public final String toString()
953         {
954             return "RelativeWidthGenerator [factor=" + this.factor + "]";
955         }
956 
957     }
958 
959     /* ******************************************************************************************************************** */
960     /* ******************************************************************************************************************** */
961     /* ******************************************************************************************************************** */
962     /* ********************************************* PARALLEL IMPLEMENTATION ********************************************** */
963     /* ******************************************************************************************************************** */
964     /* ******************************************************************************************************************** */
965     /* ******************************************************************************************************************** */
966 
967     /**
968      * Build conflicts on network; parallel implementation.
969      * @param network network
970      * @param simulator simulator
971      * @param widthGenerator width generator
972      */
973     public static void buildConflictsParallel(final RoadNetwork network, final OtsSimulatorInterface simulator,
974             final WidthGenerator widthGenerator)
975     {
976         buildConflictsParallel(network, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
977     }
978 
979     /**
980      * Build conflicts on network; parallel implementation.
981      * @param network network
982      * @param simulator simulator
983      * @param widthGenerator width generator
984      * @param ignoreList lane combinations to ignore
985      * @param permittedList lane combinations that are permitted by traffic control
986      */
987     public static void buildConflictsParallel(final RoadNetwork network, final OtsSimulatorInterface simulator,
988             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
989     {
990         buildConflictsParallelBig(getLanes(network), simulator, widthGenerator, ignoreList, permittedList);
991     }
992 
993     /**
994      * Build conflicts on list of lanes; parallel implementation.
995      * @param lanes lanes
996      * @param simulator simulator
997      * @param widthGenerator width generator
998      */
999     public static void buildConflictsParallel(final List<Lane> lanes, final OtsSimulatorInterface simulator,
1000             final WidthGenerator widthGenerator)
1001     {
1002         buildConflictsParallelBig(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
1003     }
1004 
1005     /**
1006      * Build conflicts on list of lanes; parallel implementation. Small jobs.
1007      * @param lanes list of Lanes
1008      * @param simulator the simulator
1009      * @param widthGenerator the width generator
1010      * @param ignoreList lane combinations to ignore
1011      * @param permittedList lane combinations that are permitted by traffic control
1012      */
1013     public static void buildConflictsParallelSmall(final List<Lane> lanes, final OtsSimulatorInterface simulator,
1014             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
1015     {
1016         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
1017         Logger.ots().trace("PARALLEL GENERATING OF CONFLICTS (SMALL JOBS). " + totalCombinations + " COMBINATIONS");
1018         long lastReported = 0;
1019         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
1020         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();
1021 
1022         // make a threadpool and execute buildConflicts for all records
1023         int cores = Runtime.getRuntime().availableProcessors();
1024         Logger.ots().trace("USING " + cores + " CORES");
1025         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
1026         AtomicInteger numberOfJobs = new AtomicInteger(0);
1027         final int maxqueue = 2 * cores;
1028 
1029         for (int i = 0; i < lanes.size(); i++)
1030         {
1031             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i - 1)) / 2;
1032             if (combinationsDone / 100000000 > lastReported)
1033             {
1034                 Logger.ots()
1035                         .debug(String.format(
1036                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
1037                                         + "conflicts, %d crossing conflicts)",
1038                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
1039                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
1040                 lastReported = combinationsDone / 100000000;
1041             }
1042             Lane lane1 = lanes.get(i);
1043             Set<Lane> down1 = lane1.nextLanes(null);
1044             Set<Lane> up1 = lane1.prevLanes(null);
1045 
1046             for (int j = i + 1; j < lanes.size(); j++)
1047             {
1048                 Lane lane2 = lanes.get(j);
1049                 if (ignoreList.contains(lane1, lane2))
1050                 {
1051                     continue;
1052                 }
1053                 // Quick contour check, skip if non-overlapping envelopes
1054                 try
1055                 {
1056                     if (!lane1.getAbsoluteContour().intersects(lane2.getAbsoluteContour()))
1057                     {
1058                         continue;
1059                     }
1060                 }
1061                 catch (Exception e)
1062                 {
1063                     Logger.ots().error("Contour problem - lane1 = [{}], lane2 = [{}]; skipped", lane1.getFullId(),
1064                             lane2.getFullId());
1065                     continue;
1066                 }
1067 
1068                 boolean permitted = permittedList.contains(lane1, lane2);
1069 
1070                 while (numberOfJobs.get() > maxqueue) // keep max maxqueue jobs in the pool
1071                 {
1072                     try
1073                     {
1074                         Thread.sleep(1);
1075                     }
1076                     catch (InterruptedException exception)
1077                     {
1078                         // ignore
1079                     }
1080                 }
1081                 numberOfJobs.incrementAndGet();
1082                 Set<Lane> down2 = lane2.nextLanes(null);
1083                 Set<Lane> up2 = lane2.prevLanes(null);
1084                 ConflictBuilderRecordSmall cbr = new ConflictBuilderRecordSmall(lane1, down1, up1, lane2, down2, up2, permitted,
1085                         simulator, widthGenerator, leftEdges, rightEdges);
1086                 executor.execute(new CbrTaskSmall(numberOfJobs, cbr));
1087             }
1088         }
1089 
1090         long time = System.currentTimeMillis();
1091         // wait max 60 sec for last maxqueue jobs
1092         while (numberOfJobs.get() > 0 && System.currentTimeMillis() - time < 60000)
1093         {
1094             try
1095             {
1096                 Thread.sleep(10);
1097             }
1098             catch (InterruptedException exception)
1099             {
1100                 // ignore
1101             }
1102         }
1103 
1104         executor.shutdown();
1105         while (!executor.isTerminated())
1106         {
1107             try
1108             {
1109                 Thread.sleep(1);
1110             }
1111             catch (InterruptedException exception)
1112             {
1113                 // ignore
1114             }
1115         }
1116 
1117         Logger.ots()
1118                 .debug(String.format(
1119                         "generating conflicts complete (generated %d merge conflicts, %d split "
1120                                 + "conflicts, %d crossing conflicts)",
1121                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
1122     }
1123 
1124     /**
1125      * Build conflicts on list of lanes; parallel implementation. Big jobs.
1126      * @param lanes list of Lanes
1127      * @param simulator the simulator
1128      * @param widthGenerator the width generator
1129      * @param ignoreList lane combinations to ignore
1130      * @param permittedList lane combinations that are permitted by traffic control
1131      */
1132     public static void buildConflictsParallelBig(final List<Lane> lanes, final OtsSimulatorInterface simulator,
1133             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
1134     {
1135         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
1136         Logger.ots().trace("PARALLEL GENERATING OF CONFLICTS (BIG JOBS). " + totalCombinations + " COMBINATIONS");
1137         long lastReported = 0;
1138         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
1139         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();
1140 
1141         // make a threadpool and execute buildConflicts for all records
1142         int cores = Runtime.getRuntime().availableProcessors();
1143         Logger.ots().trace("USING " + cores + " CORES");
1144         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
1145         AtomicInteger numberOfJobs = new AtomicInteger(0);
1146         final int maxqueue = 200;
1147 
1148         for (int i = 0; i < lanes.size(); i++)
1149         {
1150             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i - 1)) / 2;
1151             if (combinationsDone / 100000000 > lastReported)
1152             {
1153                 Logger.ots()
1154                         .debug(String.format(
1155                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
1156                                         + "conflicts, %d crossing conflicts)",
1157                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
1158                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
1159                 lastReported = combinationsDone / 100000000;
1160             }
1161 
1162             while (numberOfJobs.get() > maxqueue) // keep max maxqueue jobs in the pool
1163             {
1164                 try
1165                 {
1166                     Thread.sleep(0, 10);
1167                 }
1168                 catch (InterruptedException exception)
1169                 {
1170                     // ignore
1171                 }
1172             }
1173             numberOfJobs.incrementAndGet();
1174 
1175             ConflictBuilderRecordBig cbr = new ConflictBuilderRecordBig(i, lanes, ignoreList, permittedList, simulator,
1176                     widthGenerator, leftEdges, rightEdges);
1177             executor.execute(new CbrTaskBig(numberOfJobs, cbr));
1178 
1179         }
1180 
1181         long time = System.currentTimeMillis();
1182         // wait max 60 sec for last maxqueue jobs
1183         while (numberOfJobs.get() > 0 && System.currentTimeMillis() - time < 60000)
1184         {
1185             try
1186             {
1187                 Thread.sleep(10);
1188             }
1189             catch (InterruptedException exception)
1190             {
1191                 // ignore
1192             }
1193         }
1194 
1195         executor.shutdown();
1196         while (!executor.isTerminated())
1197         {
1198             try
1199             {
1200                 Thread.sleep(1);
1201             }
1202             catch (InterruptedException exception)
1203             {
1204                 // ignore
1205             }
1206         }
1207 
1208         Logger.ots()
1209                 .debug(String.format(
1210                         "generating conflicts complete (generated %d merge conflicts, %d split "
1211                                 + "conflicts, %d crossing conflicts)",
1212                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
1213     }
1214 
1215     /**
1216      * Build conflicts on network using only the groups of links that have been identified as candidates with conflicts;
1217      * parallel implementation.
1218      * @param network network
1219      * @param conflictCandidateMap the map of the conflicting links to implement
1220      * @param simulator simulator
1221      * @param widthGenerator width generator
1222      */
1223     public static void buildConflictsParallel(final RoadNetwork network, final Map<String, Set<Link>> conflictCandidateMap,
1224             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator)
1225     {
1226         for (String conflictId : conflictCandidateMap.keySet())
1227         {
1228             List<Lane> lanes = new ArrayList<>();
1229             for (Link link : conflictCandidateMap.get(conflictId))
1230             {
1231                 if (link instanceof CrossSectionLink)
1232                 {
1233                     for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList())
1234                     {
1235                         if (element instanceof Lane lane && !(element instanceof Shoulder))
1236                         {
1237                             lanes.add((Lane) element);
1238                         }
1239                     }
1240                 }
1241             }
1242             // TODO: make parallel
1243             buildConflicts(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList(), conflictId);
1244         }
1245     }
1246 
1247     /**
1248      * Small conflict builder task. A small task is finding all conflicts between two lanes.
1249      * <p>
1250      * Copyright (c) 2023-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1251      * <br>
1252      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
1253      * </p>
1254      * @author Alexander Verbraeck
1255      * @author Peter Knoppers
1256      * @author Wouter Schakel
1257      */
1258     static class CbrTaskSmall implements Runnable
1259     {
1260         /** Small conflict builder record. */
1261         final ConflictBuilderRecordSmall cbr;
1262 
1263         /** Number of jobs to do. */
1264         final AtomicInteger nrOfJobs;
1265 
1266         /**
1267          * Constructor.
1268          * @param nrOfJobs number of jobs to do.
1269          * @param cbr the record to execute.
1270          */
1271         CbrTaskSmall(final AtomicInteger nrOfJobs, final ConflictBuilderRecordSmall cbr)
1272         {
1273             this.nrOfJobs = nrOfJobs;
1274             this.cbr = cbr;
1275         }
1276 
1277         @Override
1278         public void run()
1279         {
1280             try
1281             {
1282                 buildConflicts(this.cbr.lane1, this.cbr.down1, this.cbr.up1, this.cbr.lane2, this.cbr.down2, this.cbr.up2,
1283                         this.cbr.permitted, this.cbr.simulator, this.cbr.widthGenerator, this.cbr.leftEdges,
1284                         this.cbr.rightEdges, false, null);
1285             }
1286             catch (NetworkException ne)
1287             {
1288                 throw new OtsRuntimeException("Conflict build with bad combination of types / rules.", ne);
1289             }
1290             this.nrOfJobs.decrementAndGet();
1291         }
1292     }
1293 
1294     /**
1295      * Small conflict builder record. Small means this holds the information to create conflicts between two lanes.
1296      * <p>
1297      * Copyright (c) 2023-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1298      * <br>
1299      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
1300      * </p>
1301      * @author Alexander Verbraeck
1302      * @author Peter Knoppers
1303      * @author Wouter Schakel
1304      * @param lane1 lane 1
1305      * @param down1 downstream lanes 1
1306      * @param up1 upstream lanes 1
1307      * @param lane2 lane 2
1308      * @param down2 downstream lane 2
1309      * @param up2 upstream lanes 2
1310      * @param permitted conflict permitted by traffic control
1311      * @param simulator simulator
1312      * @param widthGenerator width generator
1313      * @param leftEdges cache of left edge lines
1314      * @param rightEdges cache of right edge lines
1315      */
1316     @SuppressWarnings("checkstyle:visibilitymodifier")
1317     static record ConflictBuilderRecordSmall(Lane lane1, Set<Lane> down1, Set<Lane> up1, Lane lane2, Set<Lane> down2,
1318             Set<Lane> up2, boolean permitted, OtsSimulatorInterface simulator, WidthGenerator widthGenerator,
1319             Map<Lane, OtsLine2d> leftEdges, Map<Lane, OtsLine2d> rightEdges)
1320     {
1321     }
1322 
1323     /**
1324      * Large conflict builder task. A large task is finding all conflicts between one particular lane, and all lanes further in
1325      * a list.
1326      * <p>
1327      * Copyright (c) 2023-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1328      * <br>
1329      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
1330      * </p>
1331      * @author Alexander Verbraeck
1332      * @author Peter Knoppers
1333      * @author Wouter Schakel
1334      */
1335     static class CbrTaskBig implements Runnable
1336     {
1337         /** Big conflict builder record. */
1338         final ConflictBuilderRecordBig cbr;
1339 
1340         /** Number of jobs to do. */
1341         final AtomicInteger nrOfJobs;
1342 
1343         /**
1344          * Constructor.
1345          * @param nrOfJobs number of jobs to do.
1346          * @param cbr the record to execute.
1347          */
1348         CbrTaskBig(final AtomicInteger nrOfJobs, final ConflictBuilderRecordBig cbr)
1349         {
1350             this.nrOfJobs = nrOfJobs;
1351             this.cbr = cbr;
1352         }
1353 
1354         @Override
1355         public void run()
1356         {
1357             try
1358             {
1359                 Lane lane1 = this.cbr.lanes.get(this.cbr.starti);
1360                 Set<Lane> up1 = lane1.prevLanes(null);
1361                 Set<Lane> down1 = lane1.nextLanes(null);
1362                 for (int j = this.cbr.starti + 1; j < this.cbr.lanes.size(); j++)
1363                 {
1364                     Lane lane2 = this.cbr.lanes.get(j);
1365                     if (this.cbr.ignoreList.contains(lane1, lane2))
1366                     {
1367                         continue;
1368                     }
1369                     // Quick contour check, skip if non-overlapping envelopes
1370                     try
1371                     {
1372                         if (!lane1.getAbsoluteContour().intersects(lane2.getAbsoluteContour()))
1373                         {
1374                             continue;
1375                         }
1376                     }
1377                     catch (Exception e)
1378                     {
1379                         Logger.ots().error("Contour problem - lane1 = [{}], lane2 = [{}]; skipped", lane1.getFullId(),
1380                                 lane2.getFullId());
1381                         continue;
1382                     }
1383 
1384                     boolean permitted = this.cbr.permittedList.contains(lane1, lane2);
1385 
1386                     Set<Lane> down2 = lane2.nextLanes(null);
1387                     Set<Lane> up2 = lane2.prevLanes(null);
1388 
1389                     try
1390                     {
1391                         buildConflicts(lane1, down1, up1, lane2, down2, up2, permitted, this.cbr.simulator,
1392                                 this.cbr.widthGenerator, this.cbr.leftEdges, this.cbr.rightEdges, false, null);
1393                     }
1394                     catch (NetworkException ne)
1395                     {
1396                         Logger.ots().error(ne, "Conflict build with bad combination of types / rules.");
1397                     }
1398                 }
1399 
1400             }
1401             catch (Exception e)
1402             {
1403                 e.printStackTrace();
1404             }
1405             this.nrOfJobs.decrementAndGet();
1406         }
1407     }
1408 
1409     /**
1410      * Big conflict builder record. Big means this holds the information to create conflicts between one particular lane, and
1411      * all lanes further in a list.
1412      * <p>
1413      * Copyright (c) 2023-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1414      * <br>
1415      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
1416      * </p>
1417      * @author Alexander Verbraeck
1418      * @author Peter Knoppers
1419      * @author Wouter Schakel
1420      * @param starti the start index
1421      * @param lanes List of lanes
1422      * @param ignoreList list of lane combinations to ignore
1423      * @param permittedList list of lane combinations to permit
1424      * @param simulator simulator
1425      * @param widthGenerator width generator
1426      * @param leftEdges cache of left edge lines
1427      * @param rightEdges cache of right edge lines
1428      */
1429     @SuppressWarnings("checkstyle:visibilitymodifier")
1430     static record ConflictBuilderRecordBig(int starti, List<Lane> lanes, LaneCombinationList ignoreList,
1431             LaneCombinationList permittedList, OtsSimulatorInterface simulator, WidthGenerator widthGenerator,
1432             Map<Lane, OtsLine2d> leftEdges, Map<Lane, OtsLine2d> rightEdges)
1433     {
1434     }
1435 
1436 }