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.Throw;
9 import org.djutils.exceptions.Try;
10 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
11 import org.opentrafficsim.core.geometry.OTSLine3D;
12 import org.opentrafficsim.core.geometry.OTSPoint3D;
13 import org.opentrafficsim.core.network.LinkType;
14 import org.opentrafficsim.core.network.Network;
15 import org.opentrafficsim.core.network.NetworkException;
16 import org.opentrafficsim.core.network.OTSLink;
17 import org.opentrafficsim.road.network.RoadNetwork;
18 import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
19
20 import nl.tudelft.simulation.language.d3.DirectedPoint;
21
22 /**
23 * A CrossSectionLink is a link with lanes where GTUs can possibly switch between lanes.
24 * <p>
25 * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
26 * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
27 * <p>
28 * $LastChangedDate: 2015-09-16 19:20:07 +0200 (Wed, 16 Sep 2015) $, @version $Revision: 1405 $, by $Author: averbraeck $,
29 * initial version Aug 19, 2014 <br>
30 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
31 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
32 * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
33 */
34 public class CrossSectionLink extends OTSLink implements Serializable
35 {
36 /** */
37 private static final long serialVersionUID = 20141015L;
38
39 /** List of cross-section elements. */
40 private final List<CrossSectionElement> crossSectionElementList = new ArrayList<>();
41
42 /** List of lanes. */
43 private final List<Lane> lanes = new ArrayList<>();
44
45 /** The policy to generally keep left, keep right, or keep lane. */
46 private final LaneKeepingPolicy laneKeepingPolicy;
47
48 /** Priority. */
49 // TODO per GTUDirectionality / LongitudinalDirectionality?
50 private Priority priority = Priority.NONE;
51
52 /** Fraction in range 0...1 to divide origin or destination flow over connectors. */
53 private Double demandWeight = null;
54
55 /** Line over which GTUs enter or leave the link at the start node. */
56 private OTSLine3D startLine;
57
58 /** Line over which GTUs enter or leave the link at the end node. */
59 private OTSLine3D endLine;
60
61 /**
62 * The (regular, not timed) event type for pub/sub indicating the addition of a Lane to a CrossSectionLink. <br>
63 * Payload: Object[] { String networkId, String linkId, String LaneId, Lane lane, int laneNumber } <br>
64 * TODO work in a different way with lane numbers to align to standard lane numbering.
65 */
66 public static final EventType LANE_ADD_EVENT = new EventType("LINK.LANE.ADD");
67
68 /**
69 * The (regular, not timed) event type for pub/sub indicating the removal of a Lane from a CrossSectionLink. <br>
70 * Payload: Object[] { String networkId, String linkId, String LaneId } <br>
71 * TODO allow for the removal of a Lane; currently this is not possible.
72 */
73 public static final EventType LANE_REMOVE_EVENT = new EventType("LINK.LANE.REMOVE");
74
75 /**
76 * Construction of a cross section link.
77 * @param network RoadNetwork; the network
78 * @param id String; the link id.
79 * @param startNode OTSRoadNode; the start node (directional).
80 * @param endNode OTSRoadNode; the end node (directional).
81 * @param linkType LinkType; the link type
82 * @param designLine OTSLine3D; the design line of the Link
83 * @param simulator OTSSimulatorInterface; the simulator on which events can be scheduled
84 * @param laneKeepingPolicy LaneKeepingPolicy; the policy to generally keep left, keep right, or keep lane
85 * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
86 * or the end node of the link are not registered in the network.
87 */
88 @SuppressWarnings("checkstyle:parameternumber")
89 public CrossSectionLink(final RoadNetwork network, final String id, final OTSRoadNodene/OTSRoadNode.html#OTSRoadNode">OTSRoadNode startNode, final OTSRoadNode endNode,
90 final LinkType linkType, final OTSLine3D designLine, final OTSSimulatorInterface simulator,
91 final LaneKeepingPolicy laneKeepingPolicy) throws NetworkException
92 {
93 super(network, id, startNode, endNode, linkType, designLine, simulator);
94 this.laneKeepingPolicy = laneKeepingPolicy;
95 }
96
97 /**
98 * Clone a CrossSectionLink for a new network.
99 * @param newNetwork Network; the new network to which the clone belongs
100 * @param newSimulator OTSSimulatorInterface; the new simulator for this network
101 * @param link CrossSectionLink; the link to clone from
102 * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
103 * or the end node of the link are not registered in the network.
104 */
105 protected CrossSectionLink(final RoadNetwork newNetwork, final OTSSimulatorInterface newSimulator,
106 final CrossSectionLink link) throws NetworkException
107 {
108 super(newNetwork, newSimulator, link);
109 this.laneKeepingPolicy = link.laneKeepingPolicy;
110 for (CrossSectionElement cse : link.crossSectionElementList)
111 {
112 cse.clone(this, newSimulator);
113 // the CrossSectionElement will add itself to the Link (OTS-237)
114 }
115 }
116
117 /** {@inheritDoc} */
118 @Override
119 public RoadNetwork getNetwork()
120 {
121 return (RoadNetwork) super.getNetwork();
122 }
123
124 /**
125 * Add a cross section element at the end of the list. <br>
126 * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction.
127 * @param cse CrossSectionElement; the cross section element to add.
128 */
129 protected final void addCrossSectionElement(final CrossSectionElement cse)
130 {
131 this.crossSectionElementList.add(cse);
132 if (cse instanceof Lane)
133 {
134 this.lanes.add((Lane) cse);
135 fireEvent(LANE_ADD_EVENT,
136 new Object[] { getNetwork().getId(), getId(), cse.getId(), (Lane) cse, this.lanes.indexOf(cse) });
137 }
138 }
139
140 /**
141 * Retrieve a safe copy of the cross section element list.
142 * @return List<CrossSectionElement>; the cross section element list.
143 */
144 public final List<CrossSectionElement> getCrossSectionElementList()
145 {
146 return this.crossSectionElementList == null ? new ArrayList<>() : new ArrayList<>(this.crossSectionElementList);
147 }
148
149 /**
150 * Retrieve the lane keeping policy.
151 * @return LaneKeepingPolicy; the lane keeping policy on this CrossSectionLink
152 */
153 public final LaneKeepingPolicy getLaneKeepingPolicy()
154 {
155 return this.laneKeepingPolicy;
156 }
157
158 /**
159 * Find a cross section element with a specified id.
160 * @param id String; the id to search for
161 * @return CrossSectionElement; the cross section element with the given id, or null if not found
162 */
163 public final CrossSectionElement getCrossSectionElement(final String id)
164 {
165 for (CrossSectionElement cse : this.crossSectionElementList)
166 {
167 if (cse.getId().equals(id))
168 {
169 return cse;
170 }
171 }
172 return null;
173 }
174
175 /**
176 * Return a safe copy of the list of lanes of this CrossSectionLink.
177 * @return List<Lane>; the list of lanes.
178 */
179 public final List<Lane> getLanes()
180 {
181 return this.lanes == null ? new ArrayList<>() : new ArrayList<>(this.lanes);
182 }
183
184 /**
185 * @return priority.
186 */
187 public final Priority getPriority()
188 {
189 return this.priority;
190 }
191
192 /**
193 * @param priority Priority; set priority.
194 */
195 public final void setPriority(final Priority priority)
196 {
197 this.priority = priority;
198 }
199
200 /**
201 * Sets the demand weight. This is only applicable to links of type CONNECTOR.
202 * @param demandWeight double; demand weight, which is any positive value
203 */
204 public final void setDemandWeight(final double demandWeight)
205 {
206 Throw.when(demandWeight < 0.0, IllegalArgumentException.class, "Demand weight should be positive.");
207 Throw.when(!getLinkType().isConnector(), IllegalArgumentException.class,
208 "Demand weight can only be set on connectors.");
209 this.demandWeight = demandWeight;
210 }
211
212 /**
213 * Clears the demand weight. This is only applicable to links of type CONNECTOR.
214 */
215 public final void clearDemandWeight()
216 {
217 this.demandWeight = null;
218 }
219
220 /**
221 * Returns the demand weight. This is only applicable to links of type CONNECTOR.
222 * @return Double; demand weight, any positive value, or {@code null}
223 */
224 public final Double getDemandWeight()
225 {
226 return this.demandWeight;
227 }
228
229 /**
230 * Returns the line over which GTUs enter and leave the link at the start node.
231 * @return OTSLine3D; line over which GTUs enter and leave the link at the start node
232 */
233 public OTSLine3D getStartLine()
234 {
235 if (this.startLine == null)
236 {
237 double left = Double.NaN;
238 double right = Double.NaN;
239 for (Lane lane : this.lanes)
240 {
241 double half = lane.getBeginWidth().si * .5;
242 if (!Double.isNaN(left))
243 {
244 left = Math.max(left, lane.getDesignLineOffsetAtBegin().si + half);
245 right = Math.min(right, lane.getDesignLineOffsetAtBegin().si - half);
246 }
247 else
248 {
249 left = lane.getDesignLineOffsetAtBegin().si + half;
250 right = lane.getDesignLineOffsetAtBegin().si - half;
251 }
252 }
253 OTSPoint3D start = getStartNode().getPoint();
254 double heading = getStartNode().getHeading() + .5 * Math.PI;
255 double cosHeading = Math.cos(heading);
256 double sinHeading = Math.sin(heading);
257 OTSPoint3D leftPoint = new OTSPoint3D(start.x + cosHeading * left, start.y + sinHeading * left);
258 OTSPoint3D rightPoint = new OTSPoint3D(start.x - cosHeading * right, start.y - sinHeading * right);
259 this.startLine = Try.assign(() -> new OTSLine3D(leftPoint, rightPoint), "Invalid startline on CrossSectionLink.");
260 }
261 return this.startLine;
262 }
263
264 /**
265 * Returns the line over which GTUs enter and leave the link at the end node.
266 * @return OTSLine3D; line over which GTUs enter and leave the link at the end node
267 */
268 public OTSLine3D getEndLine()
269 {
270 if (this.endLine == null)
271 {
272 double left = Double.NaN;
273 double right = Double.NaN;
274 for (Lane lane : this.lanes)
275 {
276 double half = lane.getEndWidth().si * .5;
277 if (!Double.isNaN(left))
278 {
279 left = Math.max(left, lane.getDesignLineOffsetAtEnd().si + half);
280 right = Math.min(right, lane.getDesignLineOffsetAtEnd().si - half);
281 }
282 else
283 {
284 left = lane.getDesignLineOffsetAtEnd().si + half;
285 right = lane.getDesignLineOffsetAtEnd().si - half;
286 }
287 }
288 OTSPoint3D start = getEndNode().getPoint();
289 DirectedPoint p = Try.assign(() -> getEndNode().getLocation(), "Unexpected remote exception.");
290 double heading = p.getRotZ() + .5 * Math.PI;
291 double cosHeading = Math.cos(heading);
292 double sinHeading = Math.sin(heading);
293 OTSPoint3D leftPoint = new OTSPoint3D(start.x + cosHeading * left, start.y + sinHeading * left);
294 OTSPoint3D rightPoint = new OTSPoint3D(start.x + cosHeading * right, start.y + sinHeading * right);
295 this.endLine = Try.assign(() -> new OTSLine3D(leftPoint, rightPoint), "Invalid endline on CrossSectionLink.");
296 }
297 return this.endLine;
298 }
299
300 /** {@inheritDoc} */
301 @Override
302 public final String toString()
303 {
304 return "CrossSectionLink [name=" + this.getId() + ", nodes=" + getStartNode().getId() + "-" + getEndNode().getId()
305 + ", crossSectionElementList=" + this.crossSectionElementList + ", lanes=" + this.lanes + ", laneKeepingPolicy="
306 + this.laneKeepingPolicy + "]";
307 }
308
309 /** {@inheritDoc} */
310 @Override
311 @SuppressWarnings("checkstyle:designforextension")
312 public CrossSectionLink clone(final Network newNetwork, final OTSSimulatorInterface newSimulator) throws NetworkException
313 {
314 Throw.when(!(newNetwork instanceof RoadNetwork), NetworkException.class,
315 "CrossSectionLink.clone. newNetwork not of the type Roadnetwork");
316 return new CrossSectionLink((RoadNetwork) newNetwork, newSimulator, this);
317 }
318
319 /**
320 * Priority of a link.
321 * <p>
322 * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
323 * <br>
324 * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
325 * <p>
326 * @version $Revision$, $LastChangedDate$, by $Author$, initial version 12 dec. 2016 <br>
327 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
328 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
329 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
330 */
331 public enum Priority
332 {
333 /** Traffic has priority. */
334 PRIORITY,
335
336 /** No priority. */
337 NONE,
338
339 /** Turn on red. */
340 TURN_ON_RED,
341
342 /** Yield. */
343 YIELD,
344
345 /** Need to stop. */
346 STOP,
347
348 /** Priority according to all-stop rules. */
349 ALL_STOP,
350
351 /** Priority at bus stop, i.e. bus has right of way if it wants to leave the bus stop. */
352 BUS_STOP;
353
354 /**
355 * Returns whether this is priority.
356 * @return whether this is priority
357 */
358 public boolean isPriority()
359 {
360 return this.equals(PRIORITY);
361 }
362
363 /**
364 * Returns whether this is none.
365 * @return whether this is none
366 */
367 public boolean isNone()
368 {
369 return this.equals(NONE);
370 }
371
372 /**
373 * Returns whether this is turn on red.
374 * @return whether this is turn on red
375 */
376 public boolean isTurnOnRed()
377 {
378 return this.equals(TURN_ON_RED);
379 }
380
381 /**
382 * Returns whether this is yield.
383 * @return whether this is yield
384 */
385 public boolean isYield()
386 {
387 return this.equals(YIELD);
388 }
389
390 /**
391 * Returns whether this is stop.
392 * @return whether this is stop
393 */
394 public boolean isStop()
395 {
396 return this.equals(STOP);
397 }
398
399 /**
400 * Returns whether this is all-stop.
401 * @return whether this is all-stop
402 */
403 public boolean isAllStop()
404 {
405 return this.equals(ALL_STOP);
406 }
407
408 /**
409 * Returns whether this is bus stop.
410 * @return whether this is bus stop
411 */
412 public boolean isBusStop()
413 {
414 return this.equals(BUS_STOP);
415 }
416
417 }
418
419 }