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.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.RoadNetwork;
28  import org.opentrafficsim.road.network.lane.CrossSectionElement;
29  import org.opentrafficsim.road.network.lane.CrossSectionLink;
30  import org.opentrafficsim.road.network.lane.Lane;
31  import org.opentrafficsim.road.network.lane.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-2024 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 <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
47   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
48   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
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{}", paddedConflictId);
362                         fraction1 = 0;
363                         fraction2 = 0;
364                     }
365                     // Build conflict
366                     buildMergeConflict(lane1, fraction1, lane2, fraction2, simulator, widthGenerator, permitted);
367                     // Skip loop for efficiency, and do not create multiple merges in case of multiple same downstream lanes
368                     merge = true;
369                 }
370             }
371         }
372 
373         // Create split
374         iterator1 = up1.iterator();
375         iterator2 = up2.iterator();
376         boolean split = false;
377         while (iterator1.hasNext() && !split)
378         {
379             Lane prev1 = iterator1.next();
380             while (iterator2.hasNext() && !split)
381             {
382                 Lane prev2 = iterator2.next();
383                 if (prev1.equals(prev2))
384                 {
385                     // Same upstream lane, so a split
386                     double fraction1 = Double.NaN;
387                     double fraction2 = Double.NaN;
388                     for (Intersection intersection : intersections)
389                     {
390                         // Only consider left/right and right/left intersections (others may or may not be at the start)
391                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
392                         {
393                             fraction1 = intersection.getFraction1();
394                             fraction2 = intersection.getFraction2();
395                             break; // Split so first, not last
396                         }
397                     }
398                     // Remove all intersections up to this point, these are the result of line starts/ends matching
399                     Iterator<Intersection> iterator = intersections.iterator();
400                     while (iterator.hasNext())
401                     {
402                         if (iterator.next().getFraction1() <= fraction1)
403                         {
404                             iterator.remove();
405                         }
406                         else
407                         {
408                             // May skip further fraction
409                             break;
410                         }
411                     }
412                     if (Double.isNaN(fraction1))
413                     {
414                         Logger.ots().info("Fixing fractions of split conflict{}", paddedConflictId);
415                         fraction1 = 1;
416                         fraction2 = 1;
417                     }
418                     // Build conflict
419                     buildSplitConflict(lane1, fraction1, lane2, fraction2, simulator, widthGenerator);
420                     // Skip loop for efficiency, and do not create multiple splits in case of multiple same upstream lanes
421                     split = true;
422                 }
423             }
424         }
425 
426         // Create crossings
427         if (!lane1.getLink().equals(lane2.getLink())) // tight inner-curves with dedicated Bezier ignored
428         {
429             boolean[] crossed = new boolean[4];
430             Iterator<Intersection> iterator = intersections.iterator();
431             double f1Start = Double.NaN;
432             double f2Start = Double.NaN;
433             double f2End = Double.NaN;
434             while (iterator.hasNext())
435             {
436                 Intersection intersection = iterator.next();
437                 // First fraction found is start of conflict
438                 if (Double.isNaN(f1Start))
439                 {
440                     f1Start = intersection.getFraction1();
441                 }
442                 f2Start = Double.isNaN(f2Start) ? intersection.getFraction2() : Math.min(f2Start, intersection.getFraction2());
443                 f2End = Double.isNaN(f2End) ? intersection.getFraction2() : Math.max(f2End, intersection.getFraction2());
444                 // Flip crossed state of intersecting line combination
445                 crossed[intersection.getCombo()] = !crossed[intersection.getCombo()];
446                 // If all crossed or all not crossed, end of conflict
447                 if ((crossed[0] && crossed[1] && crossed[2] && crossed[3])
448                         || (!crossed[0] && !crossed[1] && !crossed[2] && !crossed[3]))
449                 {
450                     if (Double.isNaN(f1Start) || Double.isNaN(f2Start) || Double.isNaN(f2End))
451                     {
452                         Logger.ots().warn("NOT YET Fixing fractions of crossing conflict{}", paddedConflictId);
453                     }
454                     buildCrossingConflict(lane1, f1Start, intersection.getFraction1(), lane2, f2Start, f2End, simulator,
455                             widthGenerator, permitted);
456                     f1Start = Double.NaN;
457                     f2Start = Double.NaN;
458                     f2End = Double.NaN;
459                 }
460             }
461         }
462 
463     }
464 
465     /**
466      * Build a merge conflict.
467      * @param lane1 lane 1
468      * @param f1start start fraction 1
469      * @param lane2 lane 2
470      * @param f2start start fraction 2
471      * @param simulator simulator
472      * @param widthGenerator width generator
473      * @param permitted conflict permitted by traffic control
474      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
475      */
476     @SuppressWarnings("checkstyle:parameternumber")
477     private static void buildMergeConflict(final Lane lane1, final double f1start, final Lane lane2, final double f2start,
478             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator, final boolean permitted)
479             throws NetworkException
480     {
481 
482         // Determine lane end from direction
483         double f1end = 1.0;
484         double f2end = 1.0;
485 
486         // Get locations and length
487         Length longitudinalPosition1 = lane1.getLength().times(f1start);
488         Length longitudinalPosition2 = lane2.getLength().times(f2start);
489         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
490         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));
491 
492         // Get geometries
493         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
494         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
495 
496         // Determine conflict rule
497         ConflictRule conflictRule;
498         if (lane1.getLink().getPriority().isBusStop() || lane2.getLink().getPriority().isBusStop())
499         {
500             Throw.when(lane1.getLink().getPriority().isBusStop() && lane2.getLink().getPriority().isBusStop(),
501                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
502             // TODO: handle bus priority on the model side
503             conflictRule = new BusStopConflictRule(simulator, DefaultsNl.BUS);
504         }
505         else
506         {
507             conflictRule = new DefaultConflictRule();
508         }
509 
510         // Make conflict
511         Conflict.generateConflictPair(ConflictType.MERGE, conflictRule, permitted, lane1, longitudinalPosition1, length1,
512                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);
513 
514         numberMergeConflicts.incrementAndGet();
515     }
516 
517     /**
518      * Build a split conflict.
519      * @param lane1 lane 1
520      * @param f1end end fraction 1
521      * @param lane2 lane 2
522      * @param f2end end fraction 2
523      * @param simulator simulator
524      * @param widthGenerator width generator
525      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
526      */
527     @SuppressWarnings("checkstyle:parameternumber")
528     private static void buildSplitConflict(final Lane lane1, final double f1end, final Lane lane2, final double f2end,
529             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator) throws NetworkException
530     {
531 
532         // Determine lane start from direction
533         double f1start = 0.0;
534         double f2start = 0.0;
535 
536         // Get locations and length
537         Length longitudinalPosition1 = lane1.getLength().times(f1start);
538         Length longitudinalPosition2 = lane2.getLength().times(f2start);
539         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
540         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));
541 
542         // Get geometries
543         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
544         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
545 
546         // Make conflict
547         Conflict.generateConflictPair(ConflictType.SPLIT, new SplitConflictRule(), false, lane1, longitudinalPosition1, length1,
548                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);
549 
550         numberSplitConflicts.incrementAndGet();
551     }
552 
553     /**
554      * Build a crossing conflict.
555      * @param lane1 lane 1
556      * @param f1start start fraction 1
557      * @param f1end end fraction 1
558      * @param lane2 lane 2
559      * @param f2start start fraction 2
560      * @param f2end end fraction 2
561      * @param simulator simulator
562      * @param widthGenerator width generator
563      * @param permitted conflict permitted by traffic control
564      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
565      */
566     @SuppressWarnings("checkstyle:parameternumber")
567     private static void buildCrossingConflict(final Lane lane1, final double f1start, final double f1end, final Lane lane2,
568             final double f2start, final double f2end, final OtsSimulatorInterface simulator,
569             final WidthGenerator widthGenerator, final boolean permitted) throws NetworkException
570     {
571 
572         // Fractions may be in opposite direction, for the start location this needs to be correct
573         // Note: for geometry (real order, not considering direction) and length (absolute value) this does not matter
574         double f1startDirected;
575         double f2startDirected;
576         if (f1end < f1start)
577         {
578             f1startDirected = f1end;
579         }
580         else
581         {
582             f1startDirected = f1start;
583         }
584         if (f2end < f2start)
585         {
586             f2startDirected = f2end;
587         }
588         else
589         {
590             f2startDirected = f2start;
591         }
592 
593         // Get locations and length
594         Length longitudinalPosition1 = lane1.getLength().times(f1startDirected);
595         Length longitudinalPosition2 = lane2.getLength().times(f2startDirected);
596         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
597         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));
598 
599         // Get geometries
600         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
601         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);
602 
603         // Determine conflict rule
604         ConflictRule conflictRule;
605         if (lane1.getLink().getPriority().isBusStop() || lane2.getLink().getPriority().isBusStop())
606         {
607             Throw.when(lane1.getLink().getPriority().isBusStop() && lane2.getLink().getPriority().isBusStop(),
608                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
609             // TODO: handle bus priority on the model side
610             conflictRule = new BusStopConflictRule(simulator, DefaultsNl.BUS);
611         }
612         else
613         {
614             conflictRule = new DefaultConflictRule();
615         }
616 
617         // Make conflict
618         Conflict.generateConflictPair(ConflictType.CROSSING, conflictRule, permitted, lane1, longitudinalPosition1, length1,
619                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);
620 
621         numberCrossConflicts.incrementAndGet();
622     }
623 
624     /**
625      * Creates geometry for conflict.
626      * @param lane lane
627      * @param fStart longitudinal fraction of start
628      * @param fEnd longitudinal fraction of end
629      * @param widthGenerator width generator
630      * @return geometry for conflict
631      */
632     private static Polygon2d getGeometry(final Lane lane, final double fStart, final double fEnd,
633             final WidthGenerator widthGenerator)
634     {
635         // extractFractional needs ordered fractions, irrespective of driving direction
636         double f1;
637         double f2;
638         if (fEnd > fStart)
639         {
640             f1 = fStart;
641             f2 = fEnd;
642         }
643         else
644         {
645             f1 = fEnd;
646             f2 = fStart;
647         }
648         if (Math.abs(f1 - f2) < 1E-8)
649         {
650             Logger.ots().debug("f1 (" + f1 + ") equals f2 (" + f2 + "); problematic lane is " + lane.toString());
651             // Fix up
652             if (f1 > 0)
653             {
654                 f1 = f1 - f1 / 1000;
655             }
656             else
657             {
658                 f2 = f2 + f2 / 1000;
659             }
660         }
661         OtsLine2d centerLine = lane.getCenterLine().extractFractional(f1, f2);
662         OtsLine2d left = centerLine.offsetLine(widthGenerator.getWidth(lane, f1) / 2, widthGenerator.getWidth(lane, f2) / 2);
663         OtsLine2d right =
664                 centerLine.offsetLine(-widthGenerator.getWidth(lane, f1) / 2, -widthGenerator.getWidth(lane, f2) / 2).reverse();
665         List<Point2d> points = new ArrayList<>(left.size() + right.size());
666         points.addAll(left.getPointList());
667         points.addAll(right.getPointList());
668         return new Polygon2d(points);
669     }
670 
671     /**
672      * Intersection holds two fractions where two lines have crossed. There is also a combo to identify which lines have been
673      * used to find the intersection.
674      * <p>
675      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
676      * <br>
677      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
678      * </p>
679      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
680      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
681      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
682      */
683     private static class Intersection implements Comparable<Intersection>
684     {
685 
686         /** Fraction on lane 1. */
687         private final double fraction1;
688 
689         /** Fraction on lane 2. */
690         private final double fraction2;
691 
692         /** Edge combination number. */
693         private final int combo;
694 
695         /**
696          * @param fraction1 fraction on lane 1
697          * @param fraction2 fraction on lane 1
698          * @param combo edge combination number
699          */
700         Intersection(final double fraction1, final double fraction2, final int combo)
701         {
702             this.fraction1 = fraction1;
703             this.fraction2 = fraction2;
704             this.combo = combo;
705         }
706 
707         /**
708          * @return fraction1.
709          */
710         public final double getFraction1()
711         {
712             return this.fraction1;
713         }
714 
715         /**
716          * @return fraction2.
717          */
718         public final double getFraction2()
719         {
720             return this.fraction2;
721         }
722 
723         /**
724          * @return combo.
725          */
726         public final int getCombo()
727         {
728             return this.combo;
729         }
730 
731         @Override
732         public int compareTo(final Intersection o)
733         {
734             int out = Double.compare(this.fraction1, o.fraction1);
735             if (out != 0)
736             {
737                 return out;
738             }
739             out = Double.compare(this.fraction2, o.fraction2);
740             if (out != 0)
741             {
742                 return out;
743             }
744             return Integer.compare(this.combo, o.combo);
745         }
746 
747         @Override
748         public int hashCode()
749         {
750             final int prime = 31;
751             int result = 1;
752             result = prime * result + this.combo;
753             long temp;
754             temp = Double.doubleToLongBits(this.fraction1);
755             result = prime * result + (int) (temp ^ (temp >>> 32));
756             temp = Double.doubleToLongBits(this.fraction2);
757             result = prime * result + (int) (temp ^ (temp >>> 32));
758             return result;
759         }
760 
761         @Override
762         public boolean equals(final Object obj)
763         {
764             if (this == obj)
765             {
766                 return true;
767             }
768             if (obj == null)
769             {
770                 return false;
771             }
772             if (getClass() != obj.getClass())
773             {
774                 return false;
775             }
776             Intersection other = (Intersection) obj;
777             if (this.combo != other.combo)
778             {
779                 return false;
780             }
781             if (Double.doubleToLongBits(this.fraction1) != Double.doubleToLongBits(other.fraction1))
782             {
783                 return false;
784             }
785             if (Double.doubleToLongBits(this.fraction2) != Double.doubleToLongBits(other.fraction2))
786             {
787                 return false;
788             }
789             return true;
790         }
791 
792         /**
793          * Returns a set of intersections, sorted by the fraction on line 1.
794          * @param line1 line 1
795          * @param line2 line 2
796          * @param combo edge combination number
797          * @return set of intersections, sorted by the fraction on line 1
798          */
799         public static SortedSet<Intersection> getIntersectionList(final OtsLine2d line1, final OtsLine2d line2, final int combo)
800         {
801             SortedSet<Intersection> out = new TreeSet<>();
802             double cumul1 = 0.0;
803             Point2d start1 = null;
804             Point2d end1 = line1.get(0);
805             for (int i = 0; i < line1.size() - 1; i++)
806             {
807                 start1 = end1;
808                 end1 = line1.get(i + 1);
809 
810                 double cumul2 = 0.0;
811                 Point2d start2 = null;
812                 Point2d end2 = line2.get(0);
813 
814                 for (int j = 0; j < line2.size() - 1; j++)
815                 {
816                     start2 = end2;
817                     end2 = line2.get(j + 1);
818 
819                     Point2d p = Point2d.intersectionOfLineSegments(start1, end1, start2, end2);
820                     if (p != null)
821                     {
822                         // Segments intersect
823                         double dx = p.x - start1.x;
824                         double dy = p.y - start1.y;
825                         double length1 = cumul1 + Math.hypot(dx, dy);
826                         dx = p.x - start2.x;
827                         dy = p.y - start2.y;
828                         double length2 = cumul2 + Math.hypot(dx, dy);
829                         out.add(new Intersection(length1 / line1.getLength(), length2 / line2.getLength(), combo));
830                     }
831 
832                     double dx = end2.x - start2.x;
833                     double dy = end2.y - start2.y;
834                     cumul2 += Math.hypot(dx, dy);
835                 }
836 
837                 double dx = end1.x - start1.x;
838                 double dy = end1.y - start1.y;
839                 cumul1 += Math.hypot(dx, dy);
840             }
841 
842             return out;
843         }
844 
845         @Override
846         public String toString()
847         {
848             return "Intersection [fraction1=" + this.fraction1 + ", fraction2=" + this.fraction2 + ", combo=" + this.combo
849                     + "]";
850         }
851 
852     }
853 
854     /**
855      * Generator for width.
856      * <p>
857      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
858      * <br>
859      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
860      * </p>
861      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
862      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
863      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
864      */
865     public interface WidthGenerator
866     {
867 
868         /**
869          * Returns the begin width of this lane.
870          * @param lane lane
871          * @param fraction fraction
872          * @return begin width of this lane
873          */
874         double getWidth(Lane lane, double fraction);
875 
876     }
877 
878     /**
879      * Generator with fixed width.
880      * <p>
881      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
882      * <br>
883      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
884      * </p>
885      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
886      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
887      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
888      */
889     public static class FixedWidthGenerator implements WidthGenerator
890     {
891 
892         /** Fixed width. */
893         private final double width;
894 
895         /**
896          * Constructor with width.
897          * @param width width
898          */
899         public FixedWidthGenerator(final Length width)
900         {
901             this.width = width.si;
902         }
903 
904         @Override
905         public final double getWidth(final Lane lane, final double fraction)
906         {
907             return this.width;
908         }
909 
910         @Override
911         public final String toString()
912         {
913             return "FixedWidthGenerator [width=" + this.width + "]";
914         }
915 
916     }
917 
918     /**
919      * Generator with width factor on actual lane width.
920      * <p>
921      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
922      * <br>
923      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
924      * </p>
925      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
926      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
927      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
928      */
929     public static class RelativeWidthGenerator implements WidthGenerator
930     {
931 
932         /** Width factor. */
933         private final double factor;
934 
935         /**
936          * Constructor with width factor.
937          * @param factor width factor
938          */
939         public RelativeWidthGenerator(final double factor)
940         {
941             this.factor = factor;
942         }
943 
944         @Override
945         public final double getWidth(final Lane lane, final double fraction)
946         {
947             return lane.getWidth(fraction).si * this.factor;
948         }
949 
950         @Override
951         public final String toString()
952         {
953             return "RelativeWidthGenerator [factor=" + this.factor + "]";
954         }
955 
956     }
957 
958     /* ******************************************************************************************************************** */
959     /* ******************************************************************************************************************** */
960     /* ******************************************************************************************************************** */
961     /* ********************************************* PARALLEL IMPLEMENTATION ********************************************** */
962     /* ******************************************************************************************************************** */
963     /* ******************************************************************************************************************** */
964     /* ******************************************************************************************************************** */
965 
966     /**
967      * Build conflicts on network; parallel implementation.
968      * @param network network
969      * @param simulator simulator
970      * @param widthGenerator width generator
971      */
972     public static void buildConflictsParallel(final RoadNetwork network, final OtsSimulatorInterface simulator,
973             final WidthGenerator widthGenerator)
974     {
975         buildConflictsParallel(network, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
976     }
977 
978     /**
979      * Build conflicts on network; parallel implementation.
980      * @param network network
981      * @param simulator simulator
982      * @param widthGenerator width generator
983      * @param ignoreList lane combinations to ignore
984      * @param permittedList lane combinations that are permitted by traffic control
985      */
986     public static void buildConflictsParallel(final RoadNetwork network, final OtsSimulatorInterface simulator,
987             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
988     {
989         buildConflictsParallelBig(getLanes(network), simulator, widthGenerator, ignoreList, permittedList);
990     }
991 
992     /**
993      * Build conflicts on list of lanes; parallel implementation.
994      * @param lanes lanes
995      * @param simulator simulator
996      * @param widthGenerator width generator
997      */
998     public static void buildConflictsParallel(final List<Lane> lanes, final OtsSimulatorInterface simulator,
999             final WidthGenerator widthGenerator)
1000     {
1001         buildConflictsParallelBig(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
1002     }
1003 
1004     /**
1005      * Build conflicts on list of lanes; parallel implementation. Small jobs.
1006      * @param lanes list of Lanes
1007      * @param simulator the simulator
1008      * @param widthGenerator the width generator
1009      * @param ignoreList lane combinations to ignore
1010      * @param permittedList lane combinations that are permitted by traffic control
1011      */
1012     public static void buildConflictsParallelSmall(final List<Lane> lanes, final OtsSimulatorInterface simulator,
1013             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
1014     {
1015         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
1016         Logger.ots().trace("PARALLEL GENERATING OF CONFLICTS (SMALL JOBS). " + totalCombinations + " COMBINATIONS");
1017         long lastReported = 0;
1018         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
1019         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();
1020 
1021         // make a threadpool and execute buildConflicts for all records
1022         int cores = Runtime.getRuntime().availableProcessors();
1023         Logger.ots().trace("USING " + cores + " CORES");
1024         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
1025         AtomicInteger numberOfJobs = new AtomicInteger(0);
1026         final int maxqueue = 2 * cores;
1027 
1028         for (int i = 0; i < lanes.size(); i++)
1029         {
1030             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i - 1)) / 2;
1031             if (combinationsDone / 100000000 > lastReported)
1032             {
1033                 Logger.ots()
1034                         .debug(String.format(
1035                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
1036                                         + "conflicts, %d crossing conflicts)",
1037                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
1038                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
1039                 lastReported = combinationsDone / 100000000;
1040             }
1041             Lane lane1 = lanes.get(i);
1042             Set<Lane> down1 = lane1.nextLanes(null);
1043             Set<Lane> up1 = lane1.prevLanes(null);
1044 
1045             for (int j = i + 1; j < lanes.size(); j++)
1046             {
1047                 Lane lane2 = lanes.get(j);
1048                 if (ignoreList.contains(lane1, lane2))
1049                 {
1050                     continue;
1051                 }
1052                 // Quick contour check, skip if non-overlapping envelopes
1053                 try
1054                 {
1055                     if (!lane1.getAbsoluteContour().intersects(lane2.getAbsoluteContour()))
1056                     {
1057                         continue;
1058                     }
1059                 }
1060                 catch (Exception e)
1061                 {
1062                     Logger.ots().error("Contour problem - lane1 = [{}], lane2 = [{}]; skipped", lane1.getFullId(),
1063                             lane2.getFullId());
1064                     continue;
1065                 }
1066 
1067                 boolean permitted = permittedList.contains(lane1, lane2);
1068 
1069                 while (numberOfJobs.get() > maxqueue) // keep max maxqueue jobs in the pool
1070                 {
1071                     try
1072                     {
1073                         Thread.sleep(1);
1074                     }
1075                     catch (InterruptedException exception)
1076                     {
1077                         // ignore
1078                     }
1079                 }
1080                 numberOfJobs.incrementAndGet();
1081                 Set<Lane> down2 = lane2.nextLanes(null);
1082                 Set<Lane> up2 = lane2.prevLanes(null);
1083                 ConflictBuilderRecordSmall cbr = new ConflictBuilderRecordSmall(lane1, down1, up1, lane2, down2, up2, permitted,
1084                         simulator, widthGenerator, leftEdges, rightEdges);
1085                 executor.execute(new CbrTaskSmall(numberOfJobs, cbr));
1086             }
1087         }
1088 
1089         long time = System.currentTimeMillis();
1090         // wait max 60 sec for last maxqueue jobs
1091         while (numberOfJobs.get() > 0 && System.currentTimeMillis() - time < 60000)
1092         {
1093             try
1094             {
1095                 Thread.sleep(10);
1096             }
1097             catch (InterruptedException exception)
1098             {
1099                 // ignore
1100             }
1101         }
1102 
1103         executor.shutdown();
1104         while (!executor.isTerminated())
1105         {
1106             try
1107             {
1108                 Thread.sleep(1);
1109             }
1110             catch (InterruptedException exception)
1111             {
1112                 // ignore
1113             }
1114         }
1115 
1116         Logger.ots()
1117                 .debug(String.format(
1118                         "generating conflicts complete (generated %d merge conflicts, %d split "
1119                                 + "conflicts, %d crossing conflicts)",
1120                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
1121     }
1122 
1123     /**
1124      * Build conflicts on list of lanes; parallel implementation. Big jobs.
1125      * @param lanes list of Lanes
1126      * @param simulator the simulator
1127      * @param widthGenerator the width generator
1128      * @param ignoreList lane combinations to ignore
1129      * @param permittedList lane combinations that are permitted by traffic control
1130      */
1131     public static void buildConflictsParallelBig(final List<Lane> lanes, final OtsSimulatorInterface simulator,
1132             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
1133     {
1134         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
1135         Logger.ots().trace("PARALLEL GENERATING OF CONFLICTS (BIG JOBS). " + totalCombinations + " COMBINATIONS");
1136         long lastReported = 0;
1137         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
1138         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();
1139 
1140         // make a threadpool and execute buildConflicts for all records
1141         int cores = Runtime.getRuntime().availableProcessors();
1142         Logger.ots().trace("USING " + cores + " CORES");
1143         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
1144         AtomicInteger numberOfJobs = new AtomicInteger(0);
1145         final int maxqueue = 200;
1146 
1147         for (int i = 0; i < lanes.size(); i++)
1148         {
1149             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i - 1)) / 2;
1150             if (combinationsDone / 100000000 > lastReported)
1151             {
1152                 Logger.ots()
1153                         .debug(String.format(
1154                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
1155                                         + "conflicts, %d crossing conflicts)",
1156                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
1157                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
1158                 lastReported = combinationsDone / 100000000;
1159             }
1160 
1161             while (numberOfJobs.get() > maxqueue) // keep max maxqueue jobs in the pool
1162             {
1163                 try
1164                 {
1165                     Thread.sleep(0, 10);
1166                 }
1167                 catch (InterruptedException exception)
1168                 {
1169                     // ignore
1170                 }
1171             }
1172             numberOfJobs.incrementAndGet();
1173 
1174             ConflictBuilderRecordBig cbr = new ConflictBuilderRecordBig(i, lanes, ignoreList, permittedList, simulator,
1175                     widthGenerator, leftEdges, rightEdges);
1176             executor.execute(new CbrTaskBig(numberOfJobs, cbr));
1177 
1178         }
1179 
1180         long time = System.currentTimeMillis();
1181         // wait max 60 sec for last maxqueue jobs
1182         while (numberOfJobs.get() > 0 && System.currentTimeMillis() - time < 60000)
1183         {
1184             try
1185             {
1186                 Thread.sleep(10);
1187             }
1188             catch (InterruptedException exception)
1189             {
1190                 // ignore
1191             }
1192         }
1193 
1194         executor.shutdown();
1195         while (!executor.isTerminated())
1196         {
1197             try
1198             {
1199                 Thread.sleep(1);
1200             }
1201             catch (InterruptedException exception)
1202             {
1203                 // ignore
1204             }
1205         }
1206 
1207         Logger.ots()
1208                 .debug(String.format(
1209                         "generating conflicts complete (generated %d merge conflicts, %d split "
1210                                 + "conflicts, %d crossing conflicts)",
1211                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
1212     }
1213 
1214     /**
1215      * Build conflicts on network using only the groups of links that have been identified as candidates with conflicts;
1216      * parallel implementation.
1217      * @param network network
1218      * @param conflictCandidateMap the map of the conflicting links to implement
1219      * @param simulator simulator
1220      * @param widthGenerator width generator
1221      */
1222     public static void buildConflictsParallel(final RoadNetwork network, final Map<String, Set<Link>> conflictCandidateMap,
1223             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator)
1224     {
1225         for (String conflictId : conflictCandidateMap.keySet())
1226         {
1227             List<Lane> lanes = new ArrayList<>();
1228             for (Link link : conflictCandidateMap.get(conflictId))
1229             {
1230                 if (link instanceof CrossSectionLink)
1231                 {
1232                     for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList())
1233                     {
1234                         if (element instanceof Lane lane && !(element instanceof Shoulder))
1235                         {
1236                             lanes.add((Lane) element);
1237                         }
1238                     }
1239                 }
1240             }
1241             // TODO: make parallel
1242             buildConflicts(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList(), conflictId);
1243             Logger.setLogLevel(Level.DEBUG);
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-2024 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 <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
1255      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
1256      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
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-2024 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 <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
1302      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
1303      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
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-2024 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 <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
1332      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
1333      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
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-2024 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 <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
1418      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
1419      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
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 }