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