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