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