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