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