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
45
46
47
48
49
50 public final class LaneFactory
51 {
52
53
54 private static final double BEZIER_MARGIN = Math.toRadians(0.5);
55
56
57 private static final CurveFlattener SEGMENTS = new CurveFlattener(64);
58
59
60 private final CrossSectionLink link;
61
62
63 private final OffsetCurve2d line;
64
65
66 private Length offset;
67
68
69 private Length laneWidth0;
70
71
72 private Length offsetStart = Length.ZERO;
73
74
75 private Length offsetEnd = Length.ZERO;
76
77
78 private LaneType laneType0;
79
80
81 private LaneSpeedLimits laneSpeedLimits0;
82
83
84 private final List<Lane> lanes = new ArrayList<>();
85
86
87 private Stripe firstStripe;
88
89
90
91
92
93
94
95
96
97
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
107
108
109
110
111
112
113
114
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
127
128
129
130
131
132 private static OffsetCurve2d makeLine(final Node from, final Node to)
133 {
134
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
159
160
161
162
163
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
185
186
187
188
189
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
211
212
213
214 public LaneFactory setOffsetStart(final Length startOffset)
215 {
216 this.offsetStart = startOffset;
217 return this;
218 }
219
220
221
222
223
224
225 public LaneFactory setOffsetEnd(final Length endOffset)
226 {
227 this.offsetEnd = endOffset;
228 return this;
229 }
230
231
232
233
234
235
236
237
238
239 public LaneFactory addLanes(final StripeData... types)
240 {
241 return addLanes(new ArrayList<>(), types);
242 }
243
244
245
246
247
248
249
250
251
252
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
289
290
291
292
293
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
341
342
343 public List<Lane> getLanes()
344 {
345 return this.lanes;
346 }
347
348
349
350
351
352
353
354
355
356
357
358
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
373
374
375
376
377
378
379
380
381
382
383
384
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
401
402
403
404
405
406
407
408
409
410
411
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
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
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
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
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
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
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
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
532
533
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
548
549
550
551
552
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 }