View Javadoc
1   package org.opentrafficsim.road.network.lane;
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.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.LaneKeepingPolicy;
19  import org.opentrafficsim.road.network.RoadNetwork;
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
32  {
33      /** List of cross-section elements. */
34      private final List<CrossSectionElement> crossSectionElementList = new ArrayList<>();
35  
36      /** List of lanes. */
37      private final List<Lane> lanes = new ArrayList<>();
38  
39      /** List of shoulders. */
40      private final List<Shoulder> shoulders = 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 the network
81       * @param id the link id.
82       * @param startNode the start node (directional).
83       * @param endNode the end node (directional).
84       * @param linkType the link type
85       * @param designLine the design line of the Link
86       * @param elevation elevation given over fractional length, may be {@code null}.
87       * @param 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 ContinuousPiecewiseLinearFunction elevation,
94              final LaneKeepingPolicy laneKeepingPolicy) throws NetworkException
95      {
96          super(network, id, startNode, endNode, linkType, designLine, elevation);
97          this.laneKeepingPolicy = laneKeepingPolicy;
98      }
99  
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 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             if (cse instanceof Shoulder)
117             {
118                 this.shoulders.add((Shoulder) cse);
119             }
120             else
121             {
122                 this.lanes.add((Lane) cse);
123                 fireTimedEvent(LANE_ADD_EVENT,
124                         new Object[] {getNetwork().getId(), getId(), cse.getId(), this.lanes.indexOf(cse)},
125                         getSimulator().getSimulatorTime());
126             }
127         }
128     }
129 
130     /**
131      * Retrieve a safe copy of the cross section element list.
132      * @return the cross section element list.
133      */
134     public final List<CrossSectionElement> getCrossSectionElementList()
135     {
136         return this.crossSectionElementList == null ? new ArrayList<>() : new ArrayList<>(this.crossSectionElementList);
137     }
138 
139     /**
140      * Retrieve the lane keeping policy.
141      * @return the lane keeping policy on this CrossSectionLink
142      */
143     public final LaneKeepingPolicy getLaneKeepingPolicy()
144     {
145         return this.laneKeepingPolicy;
146     }
147 
148     /**
149      * Find a cross section element with a specified id.
150      * @param id the id to search for
151      * @return the cross section element with the given id, empty if not found
152      */
153     public final Optional<CrossSectionElement> getCrossSectionElement(final String id)
154     {
155         for (CrossSectionElement cse : this.crossSectionElementList)
156         {
157             if (cse.getId().equals(id))
158             {
159                 return Optional.of(cse);
160             }
161         }
162         return Optional.empty();
163     }
164 
165     /**
166      * Return a safe copy of the list of lanes of this CrossSectionLink.
167      * @return the list of lanes.
168      */
169     public final List<Lane> getLanes()
170     {
171         return new ArrayList<>(this.lanes);
172     }
173 
174     /**
175      * Return a safe copy of the list of shoulders of this CrossSectionLink.
176      * @return the list of lanes.
177      */
178     public final List<Shoulder> getShoulders()
179     {
180         return new ArrayList<>(this.shoulders);
181     }
182 
183     /**
184      * Return a safe copy of the list of lanes and shoulders of this CrossSectionLink.
185      * @return the list of lanes.
186      */
187     public final List<Lane> getLanesAndShoulders()
188     {
189         List<Lane> all = new ArrayList<>(this.lanes);
190         all.addAll(this.shoulders);
191         return all;
192     }
193 
194     /**
195      * Sets the priority.
196      * @return priority.
197      */
198     public final Priority getPriority()
199     {
200         return this.priority;
201     }
202 
203     /**
204      * Returns the priority.
205      * @param priority set priority.
206      */
207     public final void setPriority(final Priority priority)
208     {
209         this.priority = priority;
210     }
211 
212     /**
213      * Returns the line over which GTUs enter and leave the link at the start node.
214      * @return line over which GTUs enter and leave the link at the start node
215      */
216     public PolyLine2d getStartLine()
217     {
218         if (this.startLine == null)
219         {
220             double left = Double.NaN;
221             double right = Double.NaN;
222             for (Lane lane : getLanesAndShoulders())
223             {
224                 double half = lane.getBeginWidth().si * .5;
225                 if (!Double.isNaN(left))
226                 {
227                     left = Math.max(left, lane.getOffsetAtBegin().si + half);
228                     right = Math.min(right, lane.getOffsetAtBegin().si - half);
229                 }
230                 else
231                 {
232                     left = lane.getOffsetAtBegin().si + half;
233                     right = lane.getOffsetAtBegin().si - half;
234                 }
235             }
236             Point2d start = getDesignLine().getFirst();
237             double heading = getStartNode().getHeading().si + .5 * Math.PI;
238             double cosHeading = Math.cos(heading);
239             double sinHeading = Math.sin(heading);
240             // Note: right is negative so same sign before cos and sin
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 }