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