View Javadoc
1   package org.opentrafficsim.road.network;
2   
3   import java.math.BigInteger;
4   import java.util.ArrayList;
5   import java.util.Collection;
6   import java.util.LinkedHashMap;
7   import java.util.LinkedHashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import org.djunits.value.vdouble.scalar.Length;
13  import org.djutils.exceptions.Throw;
14  import org.opentrafficsim.base.OtsRuntimeException;
15  import org.opentrafficsim.base.StripeElement;
16  import org.opentrafficsim.base.StripeElement.StripeLateralSync;
17  import org.opentrafficsim.core.gtu.GtuType;
18  import org.opentrafficsim.core.network.LateralDirectionality;
19  
20  /**
21   * Container for data about stripes, independent from the link and curvature.
22   * <p>
23   * Copyright (c) 2024-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
24   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
25   * </p>
26   * @author Wouter Schakel
27   */
28  public class StripeData
29  {
30  
31      /** Stripe elements. */
32      private List<StripeElement> elements;
33  
34      /** Left permeability. */
35      private final boolean left;
36  
37      /** Right permeability. */
38      private final boolean right;
39  
40      /** Lateral permeability per GTU type and direction. */
41      private final Map<GtuType, Set<LateralDirectionality>> permeabilityMap = new LinkedHashMap<>();
42  
43      /** Lateral synchronization. */
44      private StripeLateralSync lateralSync = StripeLateralSync.LINK;
45  
46      /** Phase synchronization. */
47      private StripePhaseSync phaseSync = StripePhaseSync.NONE;
48  
49      /** Period based on all stripe elements. */
50      private Double period;
51  
52      /**
53       * Constructor.
54       * @param elements list of stripe elements
55       * @param left left overall permeability
56       * @param right right overall permeability
57       */
58      public StripeData(final List<StripeElement> elements, final boolean left, final boolean right)
59      {
60          this.elements = elements;
61          this.left = left;
62          this.right = right;
63      }
64  
65      /**
66       * Returns a copy of the data. As this is a mutable object that may represent a type, each stripe should hold a copy of its
67       * input.
68       * @return copy of the data
69       */
70      // TODO this is a bad way to define types and mutable data
71      public StripeData copy()
72      {
73          StripeData out = new StripeData(new ArrayList<>(), this.left, this.right);
74          out.elements.addAll(this.elements);
75          out.permeabilityMap.putAll(this.permeabilityMap);
76          out.lateralSync = this.lateralSync;
77          out.phaseSync = this.phaseSync;
78          out.period = this.period;
79          return out;
80      }
81  
82      /**
83       * Returns the elements.
84       * @return elements
85       */
86      public List<StripeElement> getElements()
87      {
88          return this.elements;
89      }
90  
91      /**
92       * Sets the elements.
93       * @param elements elements
94       */
95      public void setElements(final List<StripeElement> elements)
96      {
97          this.elements = elements;
98          this.period = null;
99      }
100 
101     /**
102      * Add lateral permeability for a GTU type. This overrules overall stripe permeability. Add NONE to prevent lane changes.
103      * Add both LEFT and RIGHT in two calls, to enable lane changes. Add LEFT or RIGHT to enable one direction while prohibiting
104      * the other.
105      * @param gtuType GTU type to add permeability for
106      * @param lateralDirection direction to add compared to the direction of the design line
107      */
108     public void addPermeability(final GtuType gtuType, final LateralDirectionality lateralDirection)
109     {
110         if (!this.permeabilityMap.containsKey(gtuType))
111         {
112             this.permeabilityMap.put(gtuType, new LinkedHashSet<LateralDirectionality>(2));
113         }
114         this.permeabilityMap.get(gtuType).add(lateralDirection);
115     }
116 
117     /**
118      * Returns whether the given GTU type is allowed to cross the line in the given lateral direction.
119      * @param gtuType GTU type to look for.
120      * @param lateralDirection direction to look for (LEFT or RIGHT) compared to the direction of the design line.
121      * @return whether the road marker is permeable for the GTU type.
122      */
123     public final boolean isPermeable(final GtuType gtuType, final LateralDirectionality lateralDirection)
124     {
125         Throw.when(lateralDirection.isNone(), OtsRuntimeException.class,
126                 "May not request NONE lateral direction for permeability.");
127         for (GtuType testGtuType = gtuType; testGtuType != null; testGtuType = testGtuType.getParent().orElse(null))
128         {
129             Set<LateralDirectionality> set = this.permeabilityMap.get(testGtuType);
130             if (null != set)
131             {
132                 return set.contains(lateralDirection);
133             }
134         }
135         return lateralDirection.isLeft() ? this.left : this.right;
136     }
137 
138     /**
139      * Sets the lateral synchronization.
140      * @param lateralSync lateral synchronization
141      */
142     public void setLateralSync(final StripeLateralSync lateralSync)
143     {
144         this.lateralSync = lateralSync;
145     }
146 
147     /**
148      * Returns the lateral synchronization.
149      * @return lateral synchronization
150      */
151     public StripeLateralSync getLateralSync()
152     {
153         return this.lateralSync;
154     }
155 
156     /**
157      * Sets the phase synchronization.
158      * @param phaseSync phase synchronization
159      */
160     public void setPhaseSync(final StripePhaseSync phaseSync)
161     {
162         this.phaseSync = phaseSync;
163     }
164 
165     /**
166      * Returns the phase synchronization.
167      * @return phase synchronization
168      */
169     public StripePhaseSync getPhaseSync()
170     {
171         return this.phaseSync;
172     }
173 
174     /**
175      * Returns the period of the common dash pattern.
176      * @return period of the common dash pattern
177      */
178     public double getPeriod()
179     {
180         if (this.period == null)
181         {
182             this.period = getPeriod(this.elements);
183         }
184         return this.period;
185     }
186 
187     /**
188      * Returns the period after which the given line gap-dash patterns repeat as a whole. Lengths are rounded to a precision of
189      * 0.0001 to find the greatest common divisor.
190      * @param elements elements
191      * @return period
192      */
193     public static double getPeriod(final List<StripeElement> elements)
194     {
195         List<Double> lineLengths = new ArrayList<>();
196         for (StripeElement element : elements)
197         {
198             if (element.dashes() != null)
199             {
200                 double length = 0.0;
201                 for (Length gapDash : element.dashes())
202                 {
203                     length += gapDash.si;
204                 }
205                 lineLengths.add(length);
206             }
207         }
208         return getPeriod(lineLengths);
209     }
210 
211     /**
212      * Returns the period after which the given line gap-dash patterns repeat as a whole. Lengths are rounded to a precision of
213      * 0.0001 to find the greatest common divisor.
214      * @param lineLengths gap-dash pattern lengths
215      * @return period
216      */
217     private static double getPeriod(final Collection<Double> lineLengths)
218     {
219         Set<Double> set = new LinkedHashSet<>(lineLengths);
220         if (lineLengths.isEmpty())
221         {
222             return -1.0;
223         }
224         else if (set.size() == 1)
225         {
226             return ((long) (lineLengths.iterator().next() * 10000)) / 10000.0;
227         }
228         long gcd = 1L;
229         for (double length : set)
230         {
231             gcd = BigInteger.valueOf(gcd).gcd(BigInteger.valueOf((long) (length * 10000))).longValue();
232         }
233         return gcd / 10000.0;
234     }
235 
236     /**
237      * Returns the width, which is the sum of stripe elements.
238      * @return width
239      */
240     public Length getWidth()
241     {
242         return Length.ofSI(this.elements.stream().mapToDouble((e) -> e.width().si).sum());
243     }
244 
245     /**
246      * Method of stripe phase synchronization.
247      */
248     public enum StripePhaseSync
249     {
250         /** Do not synchronize. */
251         NONE(false),
252 
253         /** Synchronize phase to upstream stripe. */
254         UPSTREAM(true),
255 
256         /** Synchronize phase to downstream stripe. */
257         DOWNSTREAM(true);
258 
259         /** Whether synchronization should be applied. */
260         private final boolean sync;
261 
262         /**
263          * Constructor.
264          * @param sync whether synchronization should be applied
265          */
266         StripePhaseSync(final boolean sync)
267         {
268             this.sync = sync;
269         }
270 
271         /**
272          * Returns whether synchronization should be applied.
273          * @return whether synchronization should be applied
274          */
275         public boolean isSync()
276         {
277             return this.sync;
278         }
279     }
280 
281 }