View Javadoc
1   package org.opentrafficsim.road.network.factory;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.LinkedHashMap;
6   import java.util.List;
7   import java.util.Map;
8   
9   import javax.naming.NamingException;
10  
11  import org.djunits.unit.LengthUnit;
12  import org.djunits.value.vdouble.scalar.Length;
13  import org.djunits.value.vdouble.scalar.Speed;
14  import org.djutils.exceptions.Throw;
15  import org.djutils.exceptions.Try;
16  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
17  import org.opentrafficsim.core.geometry.Bezier;
18  import org.opentrafficsim.core.geometry.DirectedPoint;
19  import org.opentrafficsim.core.geometry.OTSGeometryException;
20  import org.opentrafficsim.core.geometry.OTSLine3D;
21  import org.opentrafficsim.core.geometry.OTSPoint3D;
22  import org.opentrafficsim.core.gtu.GTUType;
23  import org.opentrafficsim.core.network.LateralDirectionality;
24  import org.opentrafficsim.core.network.LinkType;
25  import org.opentrafficsim.core.network.NetworkException;
26  import org.opentrafficsim.core.network.OTSNode;
27  import org.opentrafficsim.road.network.OTSRoadNetwork;
28  import org.opentrafficsim.road.network.lane.CrossSectionLink;
29  import org.opentrafficsim.road.network.lane.Lane;
30  import org.opentrafficsim.road.network.lane.LaneType;
31  import org.opentrafficsim.road.network.lane.OTSRoadNode;
32  import org.opentrafficsim.road.network.lane.Shoulder;
33  import org.opentrafficsim.road.network.lane.Stripe;
34  import org.opentrafficsim.road.network.lane.Stripe.Permeable;
35  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
36  
37  /**
38   * <p>
39   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
40   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
41   * <p>
42   * $LastChangedDate: 2015-09-16 19:20:07 +0200 (Wed, 16 Sep 2015) $, @version $Revision: 1405 $, by $Author: averbraeck $,
43   * initial version 30 okt. 2014 <br>
44   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
45   */
46  public final class LaneFactory
47  {
48  
49      /** Stripe width. */
50      private static final Length STRIPE_WIDTH = Length.instantiateSI(0.2);
51  
52      /** Angle above which a Bezier curve is used over a straight line. */
53      private static final double BEZIER_MARGIN = Math.toRadians(0.5);
54  
55      /** Link. */
56      private final CrossSectionLink link;
57  
58      /** Offset for next cross section elements. Left side of lane when building left to right, and vice versa. */
59      private Length offset;
60  
61      /** Lane width to use (negative when building left to right). */
62      private Length laneWidth0;
63  
64      /** Start offset. */
65      private Length offsetStart = Length.ZERO;
66  
67      /** End offset. */
68      private Length offsetEnd = Length.ZERO;
69  
70      /** Lane type to use. */
71      private LaneType laneType0;
72  
73      /** Speed limit to use. */
74      private Speed speedLimit0;
75  
76      /** created lanes. */
77      private final List<Lane> lanes = new ArrayList<>();
78  
79      /**
80       * @param network OTSRoadNetwork; network
81       * @param from OTSRoadNode; from node
82       * @param to OTSRoadNode; to node
83       * @param type LinkType; link type
84       * @param simulator OTSSimulatorInterface; simulator
85       * @param policy LaneKeepingPolicy; lane keeping policy
86       * @throws OTSGeometryException if no valid line can be created
87       * @throws NetworkException if the link exists, or a node does not exist, in the network
88       */
89      public LaneFactory(final OTSRoadNetwork network, final OTSRoadNode from, final OTSRoadNode to, final LinkType type,
90              final OTSSimulatorInterface simulator, final LaneKeepingPolicy policy) throws OTSGeometryException, NetworkException
91      {
92          this(network, from, to, type, simulator, policy, makeLine(from, to));
93      }
94  
95      /**
96       * @param network OTSRoadNetwork; network
97       * @param from OTSRoadNode; from node
98       * @param to OTSRoadNode; to node
99       * @param type LinkType; link type
100      * @param simulator OTSSimulatorInterface; simulator
101      * @param policy LaneKeepingPolicy; lane keeping policy
102      * @param line OTSLine3D; line
103      * @throws NetworkException if the link exists, or a node does not exist, in the network
104      */
105     public LaneFactory(final OTSRoadNetwork network, final OTSRoadNode from, final OTSRoadNode to, final LinkType type,
106             final OTSSimulatorInterface simulator, final LaneKeepingPolicy policy, final OTSLine3D line) throws NetworkException
107     {
108         this.link = new CrossSectionLink(network, from.getId() + to.getId(), from, to, type, line, policy);
109     }
110 
111     /**
112      * Creates a line between two nodes. If the nodes and their directions are on a straight line, a straight line is created.
113      * Otherwise a default Bezier curve is created.
114      * @param from OTSNode; from node
115      * @param to OTSNode; to node
116      * @return OTSLine3D; line
117      * @throws OTSGeometryException if no valid line can be created
118      */
119     private static OTSLine3D makeLine(final OTSNode from, final OTSNode to) throws OTSGeometryException
120     {
121         // Straight or bezier?
122         double rotCrow = Math.atan2(to.getLocation().y - from.getLocation().y, to.getLocation().x - from.getLocation().x);
123         double dRot = from.getLocation().getRotZ() - rotCrow;
124         while (dRot < -Math.PI)
125         {
126             dRot += 2.0 * Math.PI;
127         }
128         while (dRot > Math.PI)
129         {
130             dRot -= 2.0 * Math.PI;
131         }
132         OTSLine3D line;
133         if (from.getLocation().getRotZ() != to.getLocation().getRotZ() || Math.abs(dRot) > BEZIER_MARGIN)
134         {
135             line = Bezier.cubic(from.getLocation(), to.getLocation());
136         }
137         else
138         {
139             line = new OTSLine3D(from.getPoint(), to.getPoint());
140         }
141         return line;
142     }
143 
144     /**
145      * Prepare the factory to add lanes from left to right.
146      * @param leftLanes double; number of lanes left from the link design line
147      * @param laneWidth Length; lane width
148      * @param laneType LaneType; lane type
149      * @param speedLimit Speed; speed limit
150      * @return LaneFactory this lane factory for method chaining
151      */
152     public LaneFactory leftToRight(final double leftLanes, final Length laneWidth, final LaneType laneType,
153             final Speed speedLimit)
154     {
155         this.offset = laneWidth.times(leftLanes);
156         this.laneWidth0 = laneWidth.neg();
157         this.laneType0 = laneType;
158         this.speedLimit0 = speedLimit;
159         Try.execute(
160                 () -> new Stripe(this.link, this.offset.plus(this.offsetStart), this.offset.plus(this.offsetEnd), STRIPE_WIDTH),
161                 "Unexpected exception while building link.");
162         return this;
163     }
164 
165     /**
166      * Prepare the factory to add lanes from right to left.
167      * @param rightLanes double; number of lanes right from the link design line
168      * @param laneWidth Length; lane width
169      * @param laneType LaneType; lane type
170      * @param speedLimit Speed; speed limit
171      * @return LaneFactory this lane factory for method chaining
172      */
173     public LaneFactory rightToLeft(final double rightLanes, final Length laneWidth, final LaneType laneType,
174             final Speed speedLimit)
175     {
176         this.offset = laneWidth.times(-rightLanes);
177         this.laneWidth0 = laneWidth;
178         this.laneType0 = laneType;
179         this.speedLimit0 = speedLimit;
180         Try.execute(() -> new Stripe(this.link, this.offset, this.offset, STRIPE_WIDTH),
181                 "Unexpected exception while building link.");
182         return this;
183     }
184 
185     /**
186      * Set start offset.
187      * @param startOffset Length; offset
188      * @return LaneFactory this lane factory for method chaining
189      */
190     public LaneFactory setOffsetStart(final Length startOffset)
191     {
192         this.offsetStart = startOffset;
193         return this;
194     }
195 
196     /**
197      * Set end offset.
198      * @param endOffset Length; offset
199      * @return LaneFactory this lane factory for method chaining
200      */
201     public LaneFactory setOffsetEnd(final Length endOffset)
202     {
203         this.offsetEnd = endOffset;
204         return this;
205     }
206 
207     /**
208      * Adds a lane pair for each permeable, where the permeable determines the right-hand side line when building from left to
209      * right and vice versa. The left-most line is created in {@code leftToRight()}, meaning that each permeable describes
210      * permeablility between a lane and it's right-hand neighbor, when building left to right (and vice versa). For no allowed
211      * lane changes use {@code null}. This method internally adds {@code null} to create the final continuous stripe.
212      * @param permeable Permeable...; permeable per lane pair, for N lanes N-1 should be provided
213      * @return this LaneFactory this lane factory for method chaining
214      */
215     public LaneFactory addLanes(final Permeable... permeable)
216     {
217         List<Permeable> list = new ArrayList<>(Arrays.asList(permeable));
218         list.add(null);
219         for (Permeable perm : list)
220         {
221             Length startOffset = this.offset.plus(this.laneWidth0.times(0.5)).plus(this.offsetStart);
222             Length endOffset = this.offset.plus(this.laneWidth0.times(0.5)).plus(this.offsetEnd);
223             this.lanes.add(Try.assign(
224                     () -> new Lane(this.link, "Lane " + (this.lanes.size() + 1), startOffset, endOffset, this.laneWidth0.abs(),
225                             this.laneWidth0.abs(), this.laneType0, this.speedLimit0),
226                     "Unexpected exception while building link."));
227             this.offset = this.offset.plus(this.laneWidth0);
228             Stripe stripe = Try.assign(() -> new Stripe(this.link, this.offset.plus(this.offsetStart),
229                     this.offset.plus(this.offsetEnd), STRIPE_WIDTH), "Unexpected exception while building link.");
230             if (perm != null)
231             {
232                 stripe.addPermeability(this.link.getNetwork().getGtuType(GTUType.DEFAULTS.VEHICLE), perm);
233             }
234         }
235         return this;
236     }
237 
238     /**
239      * Adds 1 or 2 shoulders to the current set of lanes.
240      * @param width Length; width of the shoulder
241      * @param lat LateralDirectionality; side of shoulder, use {@code null} or {@code NONE} for both
242      * @return LaneFactory this lane factory for method chaining
243      * @throws IllegalStateException if no lanes are defined
244      */
245     public LaneFactory addShoulder(final Length width, final LateralDirectionality lat)
246     {
247         Throw.when(this.lanes.isEmpty(), IllegalStateException.class, "Lanes should be defined before adding shoulder(s).");
248         if (lat == null || lat.isNone() || lat.isLeft())
249         {
250             Length startOffset = null;
251             Length endOffset = null;
252             for (Lane lane : this.lanes)
253             {
254                 if (startOffset == null
255                         || lane.getDesignLineOffsetAtBegin().plus(lane.getBeginWidth().times(0.5)).gt(startOffset))
256                 {
257                     startOffset = lane.getDesignLineOffsetAtBegin().plus(lane.getBeginWidth().times(0.5));
258                 }
259                 if (endOffset == null || lane.getDesignLineOffsetAtEnd().plus(lane.getEndWidth().times(0.5)).gt(endOffset))
260                 {
261                     endOffset = lane.getDesignLineOffsetAtEnd().plus(lane.getEndWidth().times(0.5));
262                 }
263             }
264             Length start = startOffset.plus(width.times(0.5));
265             Length end = endOffset.plus(width.times(0.5));
266             Try.assign(() -> new Shoulder(this.link, "Left shoulder", start, end, width, width),
267                     "Unexpected exception while building link.");
268         }
269         if (lat == null || lat.isNone() || lat.isRight())
270         {
271             Length startOffset = null;
272             Length endOffset = null;
273             for (Lane lane : this.lanes)
274             {
275                 if (startOffset == null
276                         || lane.getDesignLineOffsetAtBegin().minus(lane.getBeginWidth().times(0.5)).lt(startOffset))
277                 {
278                     startOffset = lane.getDesignLineOffsetAtBegin().minus(lane.getBeginWidth().times(0.5));
279                 }
280                 if (endOffset == null
281                         || lane.getDesignLineOffsetAtEnd().minus(lane.getEndWidth().times(0.5)).lt(endOffset))
282                 {
283                     endOffset = lane.getDesignLineOffsetAtEnd().minus(lane.getEndWidth().times(0.5));
284                 }
285             }
286             Length start = startOffset.minus(width.times(0.5));
287             Length end = endOffset.minus(width.times(0.5));
288             Try.assign(() -> new Shoulder(this.link, "Right shoulder", start, end, width, width),
289                     "Unexpected exception while building link.");
290         }
291         return this;
292     }
293 
294     /**
295      * Returns the created lanes in build order.
296      * @return List&lt;Lane&gt; created lanes in build order
297      */
298     public List<Lane> getLanes()
299     {
300         return this.lanes;
301     }
302 
303     /**
304      * Create a Link along intermediate coordinates from one Node to another.
305      * @param network RoadNetwork; the network
306      * @param name String; name of the new Link
307      * @param from OTSRoadNode; start Node of the new Link
308      * @param to OTSRoadNode; end Node of the new Link
309      * @param intermediatePoints OTSPoint3D[]; array of intermediate coordinates (may be null); the intermediate points may
310      *            contain the coordinates of the from node and to node
311      * @param simulator OTSSimulatorInterface; the simulator for this network
312      * @return Link; the newly constructed Link
313      * @throws OTSGeometryException when the design line is degenerate (only one point or duplicate point)
314      * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
315      *             or the end node of the link are not registered in the network.
316      */
317     public static CrossSectionLink makeLink(final OTSRoadNetwork network, final String name, final OTSRoadNode from, 
318             final OTSRoadNode to, final OTSPoint3D[] intermediatePoints, final OTSSimulatorInterface simulator)
319             throws OTSGeometryException, NetworkException
320     {
321         List<OTSPoint3D> pointList =
322                 intermediatePoints == null ? new ArrayList<>() : new ArrayList<>(Arrays.asList(intermediatePoints));
323         if (pointList.size() == 0 || !from.getPoint().equals(pointList.get(0)))
324         {
325             pointList.add(0, from.getPoint());
326         }
327         if (pointList.size() == 0 || !to.getPoint().equals(pointList.get(pointList.size() - 1)))
328         {
329             pointList.add(to.getPoint());
330         }
331 
332         /*-
333         // see if an intermediate point needs to be created to the start of the link in the right direction
334         OTSPoint3D s1 = pointList.get(0);
335         OTSPoint3D s2 = pointList.get(1);
336         double dy = s2.y - s1.y;
337         double dx = s2.x - s1.x;
338         double a = from.getLocation().getRotZ();
339         if (Math.abs(a - Math.atan2(dy, dx)) > 1E-6)
340         {
341             double r = Math.min(1.0, Math.sqrt(dy * dy + dx * dx) / 4.0); 
342             OTSPoint3D extra = new OTSPoint3D(s1.x + r * Math.cos(a), s1.y + r * Math.sin(a), s1.z);
343             pointList.add(1, extra);
344         }
345         
346         // see if an intermediate point needs to be created to the end of the link in the right direction
347         s1 = pointList.get(pointList.size() - 2);
348         s2 = pointList.get(pointList.size() - 1);
349         dy = s2.y - s1.y;
350         dx = s2.x - s1.x;
351         a = to.getLocation().getRotZ() - Math.PI;
352         if (Math.abs(a - Math.atan2(dy, dx)) > 1E-6)
353         {
354             double r = Math.min(1.0, Math.sqrt(dy * dy + dx * dx) / 4.0); 
355             OTSPoint3D extra = new OTSPoint3D(s2.x + r * Math.cos(a), s2.y + r * Math.sin(a), s2.z);
356             pointList.add(pointList.size() - 2, extra);
357         }
358          */
359 
360         OTSLine3D designLine = new OTSLine3D(pointList);
361         CrossSectionLink link = new CrossSectionLink(network, name, from, to, network.getLinkType(LinkType.DEFAULTS.ROAD),
362                 designLine, LaneKeepingPolicy.KEEPRIGHT);
363         return link;
364     }
365 
366     /**
367      * Create one Lane.
368      * @param link CrossSectionLink; the link that owns the new Lane
369      * @param id String; the id of this lane, should be unique within the link
370      * @param laneType LaneType; the type of the new Lane
371      * @param latPosAtStart Length; the lateral position of the new Lane with respect to the design line of the link at the
372      *            start of the link
373      * @param latPosAtEnd Length; the lateral position of the new Lane with respect to the design line of the link at the end of
374      *            the link
375      * @param width Length; the width of the new Lane
376      * @param speedLimit Speed; the speed limit on the new Lane
377      * @param simulator OTSSimulatorInterface; the simulator
378      * @return Lane
379      * @throws NetworkException on network inconsistency
380      * @throws OTSGeometryException when creation of center line or contour fails
381      */
382     @SuppressWarnings("checkstyle:parameternumber")
383     private static Lane makeLane(final CrossSectionLink link, final String id, final LaneType laneType,
384             final Length latPosAtStart, final Length latPosAtEnd, final Length width, final Speed speedLimit,
385             final OTSSimulatorInterface simulator) throws NetworkException, OTSGeometryException
386     {
387         Map<GTUType, Speed> speedMap = new LinkedHashMap<>();
388         speedMap.put(link.getNetwork().getGtuType(GTUType.DEFAULTS.VEHICLE), speedLimit);
389         Lane result = new Lane(link, id, latPosAtStart, latPosAtEnd, width, width, laneType, speedMap);
390         return result;
391     }
392 
393     /**
394      * Create a simple Lane.
395      * @param network RoadNetwork; the network
396      * @param name String; name of the Lane (and also of the Link that owns it)
397      * @param from OTSRoadNode; starting node of the new Lane
398      * @param to OTSRoadNode; ending node of the new Lane
399      * @param intermediatePoints OTSPoint3D[]; intermediate coordinates or null to create a straight road; the intermediate
400      *            points may contain the coordinates of the from node and to node
401      * @param laneType LaneType; type of the new Lane
402      * @param speedLimit Speed; the speed limit on the new Lane
403      * @param simulator OTSSimulatorInterface; the simulator
404      * @return Lane; the new Lane
405      * @throws NetworkException on network inconsistency
406      * @throws OTSGeometryException when creation of center line or contour fails
407      */
408     public static Lane makeLane(final OTSRoadNetwork network, final String name, final OTSRoadNode from, final OTSRoadNode to,
409             final OTSPoint3D[] intermediatePoints, final LaneType laneType, final Speed speedLimit,
410             final OTSSimulatorInterface simulator) throws NetworkException, OTSGeometryException
411     {
412         Length width = new Length(4.0, LengthUnit.METER);
413         final CrossSectionLink link = makeLink(network, name, from, to, intermediatePoints, simulator);
414         Length latPos = new Length(0.0, LengthUnit.METER);
415         return makeLane(link, "lane", laneType, latPos, latPos, width, speedLimit, simulator);
416     }
417 
418     /**
419      * Create a simple road with the specified number of Lanes.<br>
420      * This method returns an array of Lane. These lanes are embedded in a Link that can be accessed through the getParentLink
421      * method of the Lane.
422      * @param network RoadNetwork; the network
423      * @param name String; name of the Link
424      * @param from OTSNode; starting node of the new Lane
425      * @param to OTSNode; ending node of the new Lane
426      * @param intermediatePoints OTSPoint3D[]; intermediate coordinates or null to create a straight road; the intermediate
427      *            points may contain the coordinates of the from node and to node
428      * @param laneCount int; number of lanes in the road
429      * @param laneOffsetAtStart int; extra offset from design line in lane widths at start of link
430      * @param laneOffsetAtEnd int; extra offset from design line in lane widths at end of link
431      * @param laneType LaneType; type of the new Lanes
432      * @param speedLimit Speed; the speed limit on all lanes
433      * @param simulator OTSSimulatorInterface; the simulator
434      * @return Lane&lt;String, String&gt;[]; array containing the new Lanes
435      * @throws NetworkException on topological problems
436      * @throws OTSGeometryException when creation of center line or contour fails
437      */
438     @SuppressWarnings("checkstyle:parameternumber")
439     public static Lane[] makeMultiLane(final OTSRoadNetwork network, final String name, final OTSRoadNode from, 
440             final OTSRoadNode to, final OTSPoint3D[] intermediatePoints, final int laneCount, final int laneOffsetAtStart, 
441             final int laneOffsetAtEnd, final LaneType laneType, final Speed speedLimit, final OTSSimulatorInterface simulator)
442             throws NetworkException, OTSGeometryException
443     {
444         final CrossSectionLink link = makeLink(network, name, from, to, intermediatePoints, simulator);
445         Lane[] result = new Lane[laneCount];
446         Length width = new Length(4.0, LengthUnit.METER);
447         for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
448         {
449             // Be ware! LEFT is lateral positive, RIGHT is lateral negative.
450             Length latPosAtStart = new Length((-0.5 - laneIndex - laneOffsetAtStart) * width.getSI(), LengthUnit.SI);
451             Length latPosAtEnd = new Length((-0.5 - laneIndex - laneOffsetAtEnd) * width.getSI(), LengthUnit.SI);
452             result[laneIndex] =
453                     makeLane(link, "lane." + laneIndex, laneType, latPosAtStart, latPosAtEnd, width, speedLimit, simulator);
454         }
455         return result;
456     }
457 
458     /**
459      * Create a simple road with the specified number of Lanes.<br>
460      * This method returns an array of Lane. These lanes are embedded in a Link that can be accessed through the getParentLink
461      * method of the Lane.
462      * @param network RoadNetwork; the network
463      * @param name String; name of the Link
464      * @param from OTSRoadNode; starting node of the new Lane
465      * @param to OTSRoadNode; ending node of the new Lane
466      * @param intermediatePoints OTSPoint3D[]; intermediate coordinates or null to create a straight road; the intermediate
467      *            points may contain the coordinates of the from node and to node
468      * @param laneCount int; number of lanes in the road
469      * @param laneType LaneType; type of the new Lanes
470      * @param speedLimit Speed; Speed the speed limit (applies to all generated lanes)
471      * @param simulator OTSSimulatorInterface; the simulator
472      * @return Lane&lt;String, String&gt;[]; array containing the new Lanes
473      * @throws NamingException when names cannot be registered for animation
474      * @throws NetworkException on topological problems
475      * @throws OTSGeometryException when creation of center line or contour fails
476      */
477     @SuppressWarnings("checkstyle:parameternumber")
478     public static Lane[] makeMultiLane(final OTSRoadNetwork network, final String name, final OTSRoadNode from, final OTSRoadNode to,
479             final OTSPoint3D[] intermediatePoints, final int laneCount, final LaneType laneType, final Speed speedLimit,
480             final OTSSimulatorInterface simulator) throws NamingException, NetworkException, OTSGeometryException
481     {
482         return makeMultiLane(network, name, from, to, intermediatePoints, laneCount, 0, 0, laneType, speedLimit, simulator);
483     }
484 
485     /**
486      * Create a simple road with the specified number of Lanes, based on a Bezier curve.<br>
487      * This method returns an array of Lane. These lanes are embedded in a Link that can be accessed through the getParentLink
488      * method of the Lane.
489      * @param network RoadNetwork; the network
490      * @param name String; name of the Link
491      * @param n1 OTSRoadNode; control node for the start direction
492      * @param n2 OTSRoadNode; starting node of the new Lane
493      * @param n3 OTSRoadNode; ending node of the new Lane
494      * @param n4 OTSRoadNode; control node for the end direction
495      * @param laneCount int; number of lanes in the road
496      * @param laneOffsetAtStart int; extra offset from design line in lane widths at start of link
497      * @param laneOffsetAtEnd int; extra offset from design line in lane widths at end of link
498      * @param laneType LaneType; type of the new Lanes
499      * @param speedLimit Speed; the speed limit on all lanes
500      * @param simulator OTSSimulatorInterface; the simulator
501      * @return Lane&lt;String, String&gt;[]; array containing the new Lanes
502      * @throws NamingException when names cannot be registered for animation
503      * @throws NetworkException on topological problems
504      * @throws OTSGeometryException when creation of center line or contour fails
505      */
506     @SuppressWarnings("checkstyle:parameternumber")
507     public static Lane[] makeMultiLaneBezier(final OTSRoadNetwork network, final String name, final OTSRoadNode n1, 
508             final OTSRoadNode n2, final OTSRoadNode n3, final OTSRoadNode n4, final int laneCount, final int laneOffsetAtStart, 
509             final int laneOffsetAtEnd, final LaneType laneType, final Speed speedLimit, final OTSSimulatorInterface simulator)
510             throws NamingException, NetworkException, OTSGeometryException
511     {
512         OTSLine3D bezier = makeBezier(n1, n2, n3, n4);
513         final CrossSectionLink link = makeLink(network, name, n2, n3, bezier.getPoints(), simulator);
514         Lane[] result = new Lane[laneCount];
515         Length width = new Length(4.0, LengthUnit.METER);
516         for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
517         {
518             // Be ware! LEFT is lateral positive, RIGHT is lateral negative.
519             Length latPosAtStart = new Length((-0.5 - laneIndex - laneOffsetAtStart) * width.getSI(), LengthUnit.SI);
520             Length latPosAtEnd = new Length((-0.5 - laneIndex - laneOffsetAtEnd) * width.getSI(), LengthUnit.SI);
521             result[laneIndex] =
522                     makeLane(link, "lane." + laneIndex, laneType, latPosAtStart, latPosAtEnd, width, speedLimit, simulator);
523         }
524         return result;
525     }
526 
527     /**
528      * @param n1 OTSNode; node 1
529      * @param n2 OTSNode; node 2
530      * @param n3 OTSNode; node 3
531      * @param n4 OTSNode; node 4
532      * @return line between n2 and n3 with start-direction n1--&gt;n2 and end-direction n3--&gt;n4
533      * @throws OTSGeometryException on failure of Bezier curve creation
534      */
535     public static OTSLine3D makeBezier(final OTSNode n1, final OTSNode n2, final OTSNode n3, final OTSNode n4)
536             throws OTSGeometryException
537     {
538         OTSPoint3D p1 = n1.getPoint();
539         OTSPoint3D p2 = n2.getPoint();
540         OTSPoint3D p3 = n3.getPoint();
541         OTSPoint3D p4 = n4.getPoint();
542         DirectedPoint dp1 = new DirectedPoint(p2.x, p2.y, p2.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
543         DirectedPoint dp2 = new DirectedPoint(p3.x, p3.y, p3.z, 0.0, 0.0, Math.atan2(p4.y - p3.y, p4.x - p3.x));
544         return Bezier.cubic(dp1, dp2);
545     }
546 }