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