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