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.event.EventType;
8   import org.djutils.exceptions.Try;
9   import org.djutils.metadata.MetaData;
10  import org.djutils.metadata.ObjectDescriptor;
11  import org.opentrafficsim.core.geometry.DirectedPoint;
12  import org.opentrafficsim.core.geometry.OtsLine3d;
13  import org.opentrafficsim.core.geometry.OtsPoint3d;
14  import org.opentrafficsim.core.network.LinkType;
15  import org.opentrafficsim.core.network.NetworkException;
16  import org.opentrafficsim.core.network.Node;
17  import org.opentrafficsim.core.network.Link;
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-2023 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 OtsLine3d startLine;
50  
51      /** Line over which GTUs enter or leave the link at the end node. */
52      private OtsLine3d 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 OtsLine3d; the design line of the Link
86       * @param laneKeepingPolicy 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,
92              final Node endNode, final LinkType linkType, final OtsLine3d designLine,
93              final LaneKeepingPolicy laneKeepingPolicy) throws NetworkException
94      {
95          super(network, id, startNode, endNode, linkType, designLine);
96          this.laneKeepingPolicy = laneKeepingPolicy;
97      }
98  
99      /** {@inheritDoc} */
100     @Override
101     public RoadNetwork getNetwork()
102     {
103         return (RoadNetwork) super.getNetwork();
104     }
105 
106     /**
107      * Add a cross section element at the end of the list. <br>
108      * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction.
109      * @param cse CrossSectionElement; the cross section element to add.
110      */
111     protected final void addCrossSectionElement(final CrossSectionElement cse)
112     {
113         this.crossSectionElementList.add(cse);
114         if (cse instanceof Lane)
115         {
116             this.lanes.add((Lane) cse);
117             fireTimedEvent(LANE_ADD_EVENT, new Object[] {getNetwork().getId(), getId(), cse.getId(), this.lanes.indexOf(cse)},
118                     getSimulator().getSimulatorTime());
119         }
120     }
121 
122     /**
123      * Retrieve a safe copy of the cross section element list.
124      * @return List&lt;CrossSectionElement&gt;; the cross section element list.
125      */
126     public final List<CrossSectionElement> getCrossSectionElementList()
127     {
128         return this.crossSectionElementList == null ? new ArrayList<>() : new ArrayList<>(this.crossSectionElementList);
129     }
130 
131     /**
132      * Retrieve the lane keeping policy.
133      * @return LaneKeepingPolicy; the lane keeping policy on this CrossSectionLink
134      */
135     public final LaneKeepingPolicy getLaneKeepingPolicy()
136     {
137         return this.laneKeepingPolicy;
138     }
139 
140     /**
141      * Find a cross section element with a specified id.
142      * @param id String; the id to search for
143      * @return CrossSectionElement; the cross section element with the given id, or null if not found
144      */
145     public final CrossSectionElement getCrossSectionElement(final String id)
146     {
147         for (CrossSectionElement cse : this.crossSectionElementList)
148         {
149             if (cse.getId().equals(id))
150             {
151                 return cse;
152             }
153         }
154         return null;
155     }
156 
157     /**
158      * Return a safe copy of the list of lanes of this CrossSectionLink.
159      * @return List&lt;Lane&gt;; the list of lanes.
160      */
161     public final List<Lane> getLanes()
162     {
163         return this.lanes == null ? new ArrayList<>() : new ArrayList<>(this.lanes);
164     }
165 
166     /**
167      * @return priority.
168      */
169     public final Priority getPriority()
170     {
171         return this.priority;
172     }
173 
174     /**
175      * @param priority Priority; set priority.
176      */
177     public final void setPriority(final Priority priority)
178     {
179         this.priority = priority;
180     }
181 
182     /**
183      * Returns the line over which GTUs enter and leave the link at the start node.
184      * @return OtsLine3d; line over which GTUs enter and leave the link at the start node
185      */
186     public OtsLine3d getStartLine()
187     {
188         if (this.startLine == null)
189         {
190             double left = Double.NaN;
191             double right = Double.NaN;
192             for (Lane lane : this.lanes)
193             {
194                 double half = lane.getBeginWidth().si * .5;
195                 if (!Double.isNaN(left))
196                 {
197                     left = Math.max(left, lane.getDesignLineOffsetAtBegin().si + half);
198                     right = Math.min(right, lane.getDesignLineOffsetAtBegin().si - half);
199                 }
200                 else
201                 {
202                     left = lane.getDesignLineOffsetAtBegin().si + half;
203                     right = lane.getDesignLineOffsetAtBegin().si - half;
204                 }
205             }
206             OtsPoint3d start = getStartNode().getPoint();
207             double heading = getStartNode().getHeading().si + .5 * Math.PI;
208             double cosHeading = Math.cos(heading);
209             double sinHeading = Math.sin(heading);
210             OtsPoint3d leftPoint = new OtsPoint3d(start.x + cosHeading * left, start.y + sinHeading * left);
211             OtsPoint3d rightPoint = new OtsPoint3d(start.x - cosHeading * right, start.y - sinHeading * right);
212             this.startLine = Try.assign(() -> new OtsLine3d(leftPoint, rightPoint), "Invalid startline on CrossSectionLink.");
213         }
214         return this.startLine;
215     }
216 
217     /**
218      * Returns the line over which GTUs enter and leave the link at the end node.
219      * @return OtsLine3d; line over which GTUs enter and leave the link at the end node
220      */
221     public OtsLine3d getEndLine()
222     {
223         if (this.endLine == null)
224         {
225             double left = Double.NaN;
226             double right = Double.NaN;
227             for (Lane lane : this.lanes)
228             {
229                 double half = lane.getEndWidth().si * .5;
230                 if (!Double.isNaN(left))
231                 {
232                     left = Math.max(left, lane.getDesignLineOffsetAtEnd().si + half);
233                     right = Math.min(right, lane.getDesignLineOffsetAtEnd().si - half);
234                 }
235                 else
236                 {
237                     left = lane.getDesignLineOffsetAtEnd().si + half;
238                     right = lane.getDesignLineOffsetAtEnd().si - half;
239                 }
240             }
241             OtsPoint3d start = getEndNode().getPoint();
242             DirectedPoint p = Try.assign(() -> getEndNode().getLocation(), "Unexpected remote exception.");
243             double heading = p.getRotZ() + .5 * Math.PI;
244             double cosHeading = Math.cos(heading);
245             double sinHeading = Math.sin(heading);
246             OtsPoint3d leftPoint = new OtsPoint3d(start.x + cosHeading * left, start.y + sinHeading * left);
247             OtsPoint3d rightPoint = new OtsPoint3d(start.x + cosHeading * right, start.y + sinHeading * right);
248             this.endLine = Try.assign(() -> new OtsLine3d(leftPoint, rightPoint), "Invalid endline on CrossSectionLink.");
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-2023 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://dittlab.tudelft.nl">Wouter Schakel</a>
272      */
273     public enum Priority
274     {
275         /** Traffic has priority. */
276         PRIORITY,
277 
278         /** No priority. */
279         NONE,
280 
281         /** Turn on red. */
282         TURN_ON_RED,
283 
284         /** Yield. */
285         YIELD,
286 
287         /** Need to stop. */
288         STOP,
289 
290         /** Priority according to all-stop rules. */
291         ALL_STOP,
292 
293         /** Priority at bus stop, i.e. bus has right of way if it wants to leave the bus stop. */
294         BUS_STOP;
295 
296         /**
297          * Returns whether this is priority.
298          * @return whether this is priority
299          */
300         public boolean isPriority()
301         {
302             return this.equals(PRIORITY);
303         }
304 
305         /**
306          * Returns whether this is none.
307          * @return whether this is none
308          */
309         public boolean isNone()
310         {
311             return this.equals(NONE);
312         }
313 
314         /**
315          * Returns whether this is turn on red.
316          * @return whether this is turn on red
317          */
318         public boolean isTurnOnRed()
319         {
320             return this.equals(TURN_ON_RED);
321         }
322 
323         /**
324          * Returns whether this is yield.
325          * @return whether this is yield
326          */
327         public boolean isYield()
328         {
329             return this.equals(YIELD);
330         }
331 
332         /**
333          * Returns whether this is stop.
334          * @return whether this is stop
335          */
336         public boolean isStop()
337         {
338             return this.equals(STOP);
339         }
340 
341         /**
342          * Returns whether this is all-stop.
343          * @return whether this is all-stop
344          */
345         public boolean isAllStop()
346         {
347             return this.equals(ALL_STOP);
348         }
349 
350         /**
351          * Returns whether this is bus stop.
352          * @return whether this is bus stop
353          */
354         public boolean isBusStop()
355         {
356             return this.equals(BUS_STOP);
357         }
358 
359     }
360 
361 }