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