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