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