View Javadoc
1   package org.opentrafficsim.road.network.lane;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.List;
6   
7   import org.djutils.draw.line.PolyLine2d;
8   import org.djutils.draw.point.Point2d;
9   import org.djutils.event.EventType;
10  import org.djutils.metadata.MetaData;
11  import org.djutils.metadata.ObjectDescriptor;
12  import org.opentrafficsim.core.geometry.FractionalLengthData;
13  import org.opentrafficsim.core.geometry.OtsLine2d;
14  import org.opentrafficsim.core.network.Link;
15  import org.opentrafficsim.core.network.LinkType;
16  import org.opentrafficsim.core.network.NetworkException;
17  import org.opentrafficsim.core.network.Node;
18  import org.opentrafficsim.road.network.RoadNetwork;
19  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
20  
21  /**
22   * A CrossSectionLink is a link with lanes where GTUs can possibly switch between lanes.
23   * <p>
24   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
25   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
26   * </p>
27   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
28   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
29   * @author <a href="https://www.citg.tudelft.nl">Guus Tamminga</a>
30   */
31  public class CrossSectionLink extends Link implements Serializable
32  {
33      /** */
34      private static final long serialVersionUID = 20141015L;
35  
36      /** List of cross-section elements. */
37      private final List<CrossSectionElement> crossSectionElementList = new ArrayList<>();
38  
39      /** List of lanes. */
40      private final List<Lane> lanes = new ArrayList<>();
41  
42      /** The policy to generally keep left, keep right, or keep lane. */
43      private final LaneKeepingPolicy laneKeepingPolicy;
44  
45      /** Priority. */
46      private Priority priority = Priority.NONE;
47  
48      /** Line over which GTUs enter or leave the link at the start node. */
49      private PolyLine2d startLine;
50  
51      /** Line over which GTUs enter or leave the link at the end node. */
52      private PolyLine2d endLine;
53  
54      /**
55       * The (regular, not timed) event type for pub/sub indicating the addition of a Lane to a CrossSectionLink. <br>
56       * Payload: Object[] { String networkId, String linkId, String LaneId, int laneNumber } <br>
57       * TODO work in a different way with lane numbers to align to standard lane numbering.
58       */
59      public static final EventType LANE_ADD_EVENT = new EventType("LINK.LANE.ADD",
60              new MetaData("Lane data", "Lane data",
61                      new ObjectDescriptor[] {new ObjectDescriptor("Network id", "Network id", String.class),
62                              new ObjectDescriptor("Link id", "Link id", String.class),
63                              new ObjectDescriptor("Lane id", "Lane id", String.class),
64                              new ObjectDescriptor("Lane number", "Lane number", Integer.class)}));
65  
66      /**
67       * The (regular, not timed) event type for pub/sub indicating the removal of a Lane from a CrossSectionLink. <br>
68       * Payload: Object[] { String networkId, String linkId, String LaneId } <br>
69       * TODO allow for the removal of a Lane; currently this is not possible.
70       */
71      public static final EventType LANE_REMOVE_EVENT = new EventType("LINK.LANE.REMOVE",
72              new MetaData("Lane data", "Lane data",
73                      new ObjectDescriptor[] {new ObjectDescriptor("Network id", "Network id", String.class),
74                              new ObjectDescriptor("Link id", "Link id", String.class),
75                              new ObjectDescriptor("Lane id", "Lane id", String.class),
76                              new ObjectDescriptor("Lane number", "Lane number", Integer.class)}));
77  
78      /**
79       * Construction of a cross section link.
80       * @param network RoadNetwork; the network
81       * @param id String; the link id.
82       * @param startNode Node; the start node (directional).
83       * @param endNode Node; the end node (directional).
84       * @param linkType LinkType; the link type
85       * @param designLine OtsLine2d; the design line of the Link
86       * @param elevation FractionalLengthData; elevation given over fractional length, may be {@code null}.
87       * @param laneKeepingPolicy LaneKeepingPolicy; the policy to generally keep left, keep right, or keep lane
88       * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
89       *             or the end node of the link are not registered in the network.
90       */
91      @SuppressWarnings("checkstyle:parameternumber")
92      public CrossSectionLink(final RoadNetwork network, final String id, final Node startNode, final Node endNode,
93              final LinkType linkType, final OtsLine2d designLine, final FractionalLengthData elevation,
94              final LaneKeepingPolicy laneKeepingPolicy) throws NetworkException
95      {
96          super(network, id, startNode, endNode, linkType, designLine, elevation);
97          this.laneKeepingPolicy = laneKeepingPolicy;
98      }
99  
100     /** {@inheritDoc} */
101     @Override
102     public RoadNetwork getNetwork()
103     {
104         return (RoadNetwork) super.getNetwork();
105     }
106 
107     /**
108      * Add a cross section element at the end of the list. <br>
109      * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction.
110      * @param cse CrossSectionElement; the cross section element to add.
111      */
112     protected final void addCrossSectionElement(final CrossSectionElement cse)
113     {
114         this.crossSectionElementList.add(cse);
115         if (cse instanceof Lane)
116         {
117             this.lanes.add((Lane) cse);
118             fireTimedEvent(LANE_ADD_EVENT, new Object[] {getNetwork().getId(), getId(), cse.getId(), this.lanes.indexOf(cse)},
119                     getSimulator().getSimulatorTime());
120         }
121     }
122 
123     /**
124      * Retrieve a safe copy of the cross section element list.
125      * @return List&lt;CrossSectionElement&gt;; the cross section element list.
126      */
127     public final List<CrossSectionElement> getCrossSectionElementList()
128     {
129         return this.crossSectionElementList == null ? new ArrayList<>() : new ArrayList<>(this.crossSectionElementList);
130     }
131 
132     /**
133      * Retrieve the lane keeping policy.
134      * @return LaneKeepingPolicy; the lane keeping policy on this CrossSectionLink
135      */
136     public final LaneKeepingPolicy getLaneKeepingPolicy()
137     {
138         return this.laneKeepingPolicy;
139     }
140 
141     /**
142      * Find a cross section element with a specified id.
143      * @param id String; the id to search for
144      * @return CrossSectionElement; the cross section element with the given id, or null if not found
145      */
146     public final CrossSectionElement getCrossSectionElement(final String id)
147     {
148         for (CrossSectionElement cse : this.crossSectionElementList)
149         {
150             if (cse.getId().equals(id))
151             {
152                 return cse;
153             }
154         }
155         return null;
156     }
157 
158     /**
159      * Return a safe copy of the list of lanes of this CrossSectionLink.
160      * @return List&lt;Lane&gt;; the list of lanes.
161      */
162     public final List<Lane> getLanes()
163     {
164         return this.lanes == null ? new ArrayList<>() : new ArrayList<>(this.lanes);
165     }
166 
167     /**
168      * @return priority.
169      */
170     public final Priority getPriority()
171     {
172         return this.priority;
173     }
174 
175     /**
176      * @param priority Priority; set priority.
177      */
178     public final void setPriority(final Priority priority)
179     {
180         this.priority = priority;
181     }
182 
183     /**
184      * Returns the line over which GTUs enter and leave the link at the start node.
185      * @return PolyLine2d; line over which GTUs enter and leave the link at the start node
186      */
187     public PolyLine2d getStartLine()
188     {
189         if (this.startLine == null)
190         {
191             double left = Double.NaN;
192             double right = Double.NaN;
193             for (Lane lane : this.lanes)
194             {
195                 double half = lane.getBeginWidth().si * .5;
196                 if (!Double.isNaN(left))
197                 {
198                     left = Math.max(left, lane.getOffsetAtBegin().si + half);
199                     right = Math.min(right, lane.getOffsetAtBegin().si - half);
200                 }
201                 else
202                 {
203                     left = lane.getOffsetAtBegin().si + half;
204                     right = lane.getOffsetAtBegin().si - half;
205                 }
206             }
207             Point2d start = getDesignLine().getFirst();
208             double heading = getStartNode().getHeading().si + .5 * Math.PI;
209             double cosHeading = Math.cos(heading);
210             double sinHeading = Math.sin(heading);
211             Point2d leftPoint = new Point2d(start.x + cosHeading * left, start.y + sinHeading * left);
212             Point2d rightPoint = new Point2d(start.x - cosHeading * right, start.y - sinHeading * right);
213             this.startLine = new PolyLine2d(leftPoint, rightPoint);
214         }
215         return this.startLine;
216     }
217 
218     /**
219      * Returns the line over which GTUs enter and leave the link at the end node.
220      * @return PolyLine2d; line over which GTUs enter and leave the link at the end node
221      */
222     public PolyLine2d getEndLine()
223     {
224         if (this.endLine == null)
225         {
226             double left = Double.NaN;
227             double right = Double.NaN;
228             for (Lane lane : this.lanes)
229             {
230                 double half = lane.getEndWidth().si * .5;
231                 if (!Double.isNaN(left))
232                 {
233                     left = Math.max(left, lane.getOffsetAtEnd().si + half);
234                     right = Math.min(right, lane.getOffsetAtEnd().si - half);
235                 }
236                 else
237                 {
238                     left = lane.getOffsetAtEnd().si + half;
239                     right = lane.getOffsetAtEnd().si - half;
240                 }
241             }
242             Point2d start = getDesignLine().getLast();
243             double heading = getEndNode().getHeading().si + .5 * Math.PI;
244             double cosHeading = Math.cos(heading);
245             double sinHeading = Math.sin(heading);
246             Point2d leftPoint = new Point2d(start.x + cosHeading * left, start.y + sinHeading * left);
247             Point2d rightPoint = new Point2d(start.x + cosHeading * right, start.y + sinHeading * right);
248             this.endLine = new PolyLine2d(leftPoint, rightPoint);
249         }
250         return this.endLine;
251     }
252 
253     /** {@inheritDoc} */
254     @Override
255     public final String toString()
256     {
257         return "CrossSectionLink [name=" + this.getId() + ", nodes=" + getStartNode().getId() + "-" + getEndNode().getId()
258                 + ", crossSectionElementList=" + this.crossSectionElementList + ", lanes=" + this.lanes + ", laneKeepingPolicy="
259                 + this.laneKeepingPolicy + "]";
260     }
261 
262     /**
263      * Priority of a link.
264      * <p>
265      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
266      * <br>
267      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
268      * </p>
269      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
270      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
271      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
272      */
273     public enum Priority
274     {
275         /** Traffic has priority. */
276         PRIORITY,
277 
278         /** No priority. */
279         NONE,
280 
281         /** Yield. */
282         YIELD,
283 
284         /** Need to stop. */
285         STOP,
286 
287         /** Priority according to all-stop rules. */
288         ALL_STOP,
289 
290         /** Priority at bus stop, i.e. bus has right of way if it wants to leave the bus stop. */
291         BUS_STOP;
292 
293         /**
294          * Returns whether this is priority.
295          * @return whether this is priority
296          */
297         public boolean isPriority()
298         {
299             return this.equals(PRIORITY);
300         }
301 
302         /**
303          * Returns whether this is none.
304          * @return whether this is none
305          */
306         public boolean isNone()
307         {
308             return this.equals(NONE);
309         }
310 
311         /**
312          * Returns whether this is yield.
313          * @return whether this is yield
314          */
315         public boolean isYield()
316         {
317             return this.equals(YIELD);
318         }
319 
320         /**
321          * Returns whether this is stop.
322          * @return whether this is stop
323          */
324         public boolean isStop()
325         {
326             return this.equals(STOP);
327         }
328 
329         /**
330          * Returns whether this is all-stop.
331          * @return whether this is all-stop
332          */
333         public boolean isAllStop()
334         {
335             return this.equals(ALL_STOP);
336         }
337 
338         /**
339          * Returns whether this is bus stop.
340          * @return whether this is bus stop
341          */
342         public boolean isBusStop()
343         {
344             return this.equals(BUS_STOP);
345         }
346 
347     }
348 
349 }