1 package org.opentrafficsim.road.network.factory;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.PrimitiveIterator.OfInt;
8 import java.util.stream.IntStream;
9
10 import javax.naming.NamingException;
11
12 import org.djunits.unit.LengthUnit;
13 import org.djunits.value.vdouble.scalar.Length;
14 import org.djunits.value.vdouble.scalar.Speed;
15 import org.djutils.draw.curve.BezierCubic2d;
16 import org.djutils.draw.curve.OffsetCurve2d;
17 import org.djutils.draw.curve.Straight2d;
18 import org.djutils.draw.function.ContinuousPiecewiseLinearFunction;
19 import org.djutils.draw.line.Ray2d;
20 import org.djutils.draw.point.DirectedPoint2d;
21 import org.djutils.draw.point.Point2d;
22 import org.djutils.exceptions.Throw;
23 import org.djutils.exceptions.Try;
24 import org.opentrafficsim.base.geometry.OtsGeometryUtil;
25 import org.opentrafficsim.base.geometry.OtsLine2d;
26 import org.opentrafficsim.core.definitions.DefaultsNl;
27 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
28 import org.opentrafficsim.core.geometry.CurveFlattener;
29 import org.opentrafficsim.core.geometry.PolyLineCurve2d;
30 import org.opentrafficsim.core.gtu.GtuType;
31 import org.opentrafficsim.core.network.LateralDirectionality;
32 import org.opentrafficsim.core.network.LinkType;
33 import org.opentrafficsim.core.network.NetworkException;
34 import org.opentrafficsim.core.network.Node;
35 import org.opentrafficsim.road.definitions.DefaultsRoadNl;
36 import org.opentrafficsim.road.network.LaneKeepingPolicy;
37 import org.opentrafficsim.road.network.RoadNetwork;
38 import org.opentrafficsim.road.network.lane.CrossSectionGeometry;
39 import org.opentrafficsim.road.network.lane.CrossSectionLink;
40 import org.opentrafficsim.road.network.lane.Lane;
41 import org.opentrafficsim.road.network.lane.LaneGeometryUtil;
42 import org.opentrafficsim.road.network.lane.LaneType;
43 import org.opentrafficsim.road.network.lane.Stripe;
44 import org.opentrafficsim.road.network.lane.StripeData;
45
46
47
48
49
50
51
52
53 public final class LaneFactory
54 {
55
56
57 private static final double BEZIER_MARGIN = Math.toRadians(0.5);
58
59
60 private static final CurveFlattener SEGMENTS = new CurveFlattener(64);
61
62
63 private final CrossSectionLink link;
64
65
66 private final OffsetCurve2d line;
67
68
69 private Length offset;
70
71
72 private Length laneWidth0;
73
74
75 private Length offsetStart = Length.ZERO;
76
77
78 private Length offsetEnd = Length.ZERO;
79
80
81 private LaneType laneType0;
82
83
84 private Speed speedLimit0;
85
86
87 private GtuType gtuType;
88
89
90 private final List<Lane> lanes = new ArrayList<>();
91
92
93 private Stripe firstStripe;
94
95
96
97
98
99
100
101
102
103
104
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
115
116
117
118
119
120
121
122
123
124
125 public LaneFactory(final RoadNetwork network, final Node from, final Node to, final LinkType type,
126 final OtsSimulatorInterface simulator, final LaneKeepingPolicy policy, final GtuType gtuType,
127 final OffsetCurve2d line) throws NetworkException
128 {
129 this.link = new CrossSectionLink(network, from.getId() + to.getId(), from, to, type,
130 new OtsLine2d(line.toPolyLine(SEGMENTS)), null, policy);
131 this.line = line;
132 this.gtuType = gtuType;
133 }
134
135
136
137
138
139
140
141
142 private static OffsetCurve2d makeLine(final Node from, final Node to)
143 {
144
145 double rotCrow = Math.atan2(to.getLocation().y - from.getLocation().y, to.getLocation().x - from.getLocation().x);
146 double dRot = from.getLocation().getDirZ() - rotCrow;
147 while (dRot < -Math.PI)
148 {
149 dRot += 2.0 * Math.PI;
150 }
151 while (dRot > Math.PI)
152 {
153 dRot -= 2.0 * Math.PI;
154 }
155 OffsetCurve2d line;
156 if (from.getLocation().getDirZ() != to.getLocation().getDirZ() || Math.abs(dRot) > BEZIER_MARGIN)
157 {
158 line = new BezierCubic2d(new Ray2d(from.getLocation()), new Ray2d(to.getLocation()), 1.0, false);
159 }
160 else
161 {
162 line = new Straight2d(from.getLocation(), from.getPoint().distance(to.getPoint()));
163 }
164 return line;
165 }
166
167
168
169
170
171
172
173
174
175 public LaneFactory leftToRight(final double leftLanes, final Length laneWidth, final LaneType laneType,
176 final Speed speedLimit)
177 {
178 this.offset = laneWidth.times(leftLanes);
179 this.laneWidth0 = laneWidth.neg();
180 this.laneType0 = laneType;
181 this.speedLimit0 = speedLimit;
182 Length width = DefaultsRoadNl.SOLID.getWidth();
183 Length offsetStripeStart = this.offset.plus(this.offsetStart);
184 Length offsetStripeEnd = this.offset.plus(this.offsetEnd);
185 ContinuousPiecewiseLinearFunction offsetFunc =
186 ContinuousPiecewiseLinearFunction.of(0.0, offsetStripeStart.si, 1.0, offsetStripeEnd.si);
187 ContinuousPiecewiseLinearFunction widthFunc = ContinuousPiecewiseLinearFunction.of(0.0, width.si, 1.0, width.si);
188 this.firstStripe = Try.assign(
189 () -> new Stripe("1", DefaultsRoadNl.SOLID, this.link,
190 CrossSectionGeometry.of(this.line, SEGMENTS, offsetFunc, widthFunc)),
191 "Unexpected exception while building link.");
192 return this;
193 }
194
195
196
197
198
199
200
201
202
203 public LaneFactory rightToLeft(final double rightLanes, final Length laneWidth, final LaneType laneType,
204 final Speed speedLimit)
205 {
206 this.offset = laneWidth.times(-rightLanes);
207 this.laneWidth0 = laneWidth;
208 this.laneType0 = laneType;
209 this.speedLimit0 = speedLimit;
210 Length width = DefaultsRoadNl.SOLID.getWidth();
211 Length offsetStripeStart = this.offset.plus(this.offsetStart);
212 Length offsetStripeEnd = this.offset.plus(this.offsetEnd);
213 ContinuousPiecewiseLinearFunction offsetFunc =
214 ContinuousPiecewiseLinearFunction.of(0.0, offsetStripeStart.si, 1.0, offsetStripeEnd.si);
215 ContinuousPiecewiseLinearFunction widthFunc = ContinuousPiecewiseLinearFunction.of(0.0, width.si, 1.0, width.si);
216 this.firstStripe = Try.assign(
217 () -> new Stripe("1", DefaultsRoadNl.SOLID, this.link,
218 CrossSectionGeometry.of(this.line, SEGMENTS, offsetFunc, widthFunc)),
219 "Unexpected exception while building link.");
220 return this;
221 }
222
223
224
225
226
227
228 public LaneFactory setOffsetStart(final Length startOffset)
229 {
230 this.offsetStart = startOffset;
231 return this;
232 }
233
234
235
236
237
238
239 public LaneFactory setOffsetEnd(final Length endOffset)
240 {
241 this.offsetEnd = endOffset;
242 return this;
243 }
244
245
246
247
248
249
250
251
252
253 public LaneFactory addLanes(final StripeData... types)
254 {
255 return addLanes(new ArrayList<>(), types);
256 }
257
258
259
260
261
262
263
264
265
266
267
268 public LaneFactory addLanes(final List<? super Stripe> stripeList, final StripeData... types)
269 {
270 stripeList.add(this.firstStripe);
271 List<StripeData> typeList = new ArrayList<>(Arrays.asList(types));
272 typeList.add(DefaultsRoadNl.SOLID);
273 OfInt idStream = IntStream.rangeClosed(2, typeList.size() + 1).iterator();
274 for (StripeData type : typeList)
275 {
276 Length startOffset = this.offset.plus(this.laneWidth0.times(0.5)).plus(this.offsetStart);
277 Length endOffset = this.offset.plus(this.laneWidth0.times(0.5)).plus(this.offsetEnd);
278
279 ContinuousPiecewiseLinearFunction offsetFunc =
280 ContinuousPiecewiseLinearFunction.of(0.0, startOffset.si, 1.0, endOffset.si);
281 ContinuousPiecewiseLinearFunction widthFunc =
282 ContinuousPiecewiseLinearFunction.of(0.0, this.laneWidth0.abs().si, 1.0, this.laneWidth0.abs().si);
283
284 this.lanes.add(Try.assign(() -> new Lane(this.link, "Lane " + (this.lanes.size() + 1),
285 CrossSectionGeometry.of(this.line, SEGMENTS, offsetFunc, widthFunc), this.laneType0,
286 Map.of(this.gtuType, this.speedLimit0)), "Unexpected exception while building link."));
287 this.offset = this.offset.plus(this.laneWidth0);
288
289 Length width = type.getWidth();
290 startOffset = this.offset.plus(this.offsetStart);
291 endOffset = this.offset.plus(this.offsetEnd);
292 ContinuousPiecewiseLinearFunction offsetFunc2 =
293 ContinuousPiecewiseLinearFunction.of(0.0, startOffset.si, 1.0, endOffset.si);
294 ContinuousPiecewiseLinearFunction widthFunc2 = ContinuousPiecewiseLinearFunction.of(0.0, width.si, 1.0, width.si);
295 stripeList.add(Try.assign(
296 () -> new Stripe("" + idStream.nextInt(), type, this.link,
297 CrossSectionGeometry.of(this.line, SEGMENTS, offsetFunc2, widthFunc2)),
298 "Unexpected exception while building link."));
299 }
300 return this;
301 }
302
303
304
305
306
307
308
309
310
311 public LaneFactory addShoulder(final Length width, final LateralDirectionality lat, final LaneType laneType)
312 {
313 Throw.when(this.lanes.isEmpty(), IllegalStateException.class, "Lanes should be defined before adding shoulder(s).");
314 if (lat == null || lat.isNone() || lat.isLeft())
315 {
316 Length startOffset = null;
317 Length endOffset = null;
318 for (Lane lane : this.lanes)
319 {
320 if (startOffset == null || lane.getOffsetAtBegin().plus(lane.getBeginWidth().times(0.5)).gt(startOffset))
321 {
322 startOffset = lane.getOffsetAtBegin().plus(lane.getBeginWidth().times(0.5));
323 }
324 if (endOffset == null || lane.getOffsetAtEnd().plus(lane.getEndWidth().times(0.5)).gt(endOffset))
325 {
326 endOffset = lane.getOffsetAtEnd().plus(lane.getEndWidth().times(0.5));
327 }
328 }
329 Length start = startOffset.plus(width.times(0.5));
330 Length end = endOffset.plus(width.times(0.5));
331 Try.assign(() -> LaneGeometryUtil.createStraightShoulder(this.link, "Left shoulder", start, end, width, width,
332 laneType), "Unexpected exception while building link.");
333 }
334 if (lat == null || lat.isNone() || lat.isRight())
335 {
336 Length startOffset = null;
337 Length endOffset = null;
338 for (Lane lane : this.lanes)
339 {
340 if (startOffset == null || lane.getOffsetAtBegin().minus(lane.getBeginWidth().times(0.5)).lt(startOffset))
341 {
342 startOffset = lane.getOffsetAtBegin().minus(lane.getBeginWidth().times(0.5));
343 }
344 if (endOffset == null || lane.getOffsetAtEnd().minus(lane.getEndWidth().times(0.5)).lt(endOffset))
345 {
346 endOffset = lane.getOffsetAtEnd().minus(lane.getEndWidth().times(0.5));
347 }
348 }
349 Length start = startOffset.minus(width.times(0.5));
350 Length end = endOffset.minus(width.times(0.5));
351 Try.assign(() -> LaneGeometryUtil.createStraightShoulder(this.link, "Right shoulder", start, end, width, width,
352 laneType), "Unexpected exception while building link.");
353 }
354 return this;
355 }
356
357
358
359
360
361 public List<Lane> getLanes()
362 {
363 return this.lanes;
364 }
365
366
367
368
369
370
371
372
373
374
375
376
377
378 public static CrossSectionLink makeLink(final RoadNetwork network, final String name, final Node from, final Node to,
379 final Point2d[] intermediatePoints, final OtsSimulatorInterface simulator) throws NetworkException
380 {
381 List<Point2d> pointList = intermediatePoints == null ? List.of(from.getPoint(), to.getPoint())
382 : new ArrayList<>(Arrays.asList(intermediatePoints));
383 OtsLine2d designLine = new OtsLine2d(pointList);
384 CrossSectionLink link =
385 new CrossSectionLink(network, name, from, to, DefaultsNl.ROAD, designLine, null, LaneKeepingPolicy.KEEPRIGHT);
386 return link;
387 }
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 @SuppressWarnings("checkstyle:parameternumber")
406 private static Lane makeLane(final CrossSectionLink link, final String id, final LaneType laneType,
407 final Length latPosAtStart, final Length latPosAtEnd, final Length width, final Speed speedLimit,
408 final OtsSimulatorInterface simulator, final GtuType gtuType) throws NetworkException
409 {
410 OffsetCurve2d line = new PolyLineCurve2d(link.getDesignLine(), link.getStartNode().getLocation().dirZ,
411 link.getEndNode().getLocation().dirZ);
412 ContinuousPiecewiseLinearFunction offsetFunc =
413 ContinuousPiecewiseLinearFunction.of(0.0, latPosAtStart.si, 1.0, latPosAtEnd.si);
414 ContinuousPiecewiseLinearFunction widthFunc = ContinuousPiecewiseLinearFunction.of(0.0, width.si, 1.0, width.si);
415 return new Lane(link, id, CrossSectionGeometry.of(line, SEGMENTS, offsetFunc, widthFunc), laneType,
416 Map.of(gtuType, speedLimit));
417 }
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434 @SuppressWarnings("checkstyle:parameternumber")
435 public static Lane makeLane(final RoadNetwork network, final String name, final Node from, final Node to,
436 final Point2d[] intermediatePoints, final LaneType laneType, final Speed speedLimit,
437 final OtsSimulatorInterface simulator, final GtuType gtuType) throws NetworkException
438 {
439 Length width = new Length(4.0, LengthUnit.METER);
440 final CrossSectionLink link = makeLink(network, name, from, to, intermediatePoints, simulator);
441 Length latPos = new Length(0.0, LengthUnit.METER);
442 return makeLane(link, "lane", laneType, latPos, latPos, width, speedLimit, simulator, gtuType);
443 }
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465 @SuppressWarnings("checkstyle:parameternumber")
466 public static Lane[] makeMultiLane(final RoadNetwork network, final String name, final Node from, final Node to,
467 final Point2d[] intermediatePoints, final int laneCount, final int laneOffsetAtStart, final int laneOffsetAtEnd,
468 final LaneType laneType, final Speed speedLimit, final OtsSimulatorInterface simulator, final GtuType gtuType)
469 throws NetworkException
470 {
471 final CrossSectionLink link = makeLink(network, name, from, to, intermediatePoints, simulator);
472 Lane[] result = new Lane[laneCount];
473 Length width = new Length(4.0, LengthUnit.METER);
474 for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
475 {
476
477 Length latPosAtStart = new Length((-0.5 - laneIndex - laneOffsetAtStart) * width.getSI(), LengthUnit.SI);
478 Length latPosAtEnd = new Length((-0.5 - laneIndex - laneOffsetAtEnd) * width.getSI(), LengthUnit.SI);
479 result[laneIndex] = makeLane(link, "lane." + laneIndex, laneType, latPosAtStart, latPosAtEnd, width, speedLimit,
480 simulator, gtuType);
481 }
482 return result;
483 }
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504 @SuppressWarnings("checkstyle:parameternumber")
505 public static Lane[] makeMultiLane(final RoadNetwork network, final String name, final Node from, final Node to,
506 final Point2d[] intermediatePoints, final int laneCount, final LaneType laneType, final Speed speedLimit,
507 final OtsSimulatorInterface simulator, final GtuType gtuType) throws NamingException, NetworkException
508 {
509 return makeMultiLane(network, name, from, to, intermediatePoints, laneCount, 0, 0, laneType, speedLimit, simulator,
510 gtuType);
511 }
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534 @SuppressWarnings("checkstyle:parameternumber")
535 public static Lane[] makeMultiLaneBezier(final RoadNetwork network, final String name, final Node n1, final Node n2,
536 final Node n3, final Node n4, final int laneCount, final int laneOffsetAtStart, final int laneOffsetAtEnd,
537 final LaneType laneType, final Speed speedLimit, final OtsSimulatorInterface simulator, final GtuType gtuType)
538 throws NamingException, NetworkException
539 {
540 DirectedPoint2d dp1 = new DirectedPoint2d(n2.getPoint().x, n2.getPoint().y,
541 Math.atan2(n2.getPoint().y - n1.getPoint().y, n2.getPoint().x - n1.getPoint().x));
542 DirectedPoint2d dp2 = new DirectedPoint2d(n3.getPoint().x, n3.getPoint().y,
543 Math.atan2(n4.getPoint().y - n3.getPoint().y, n4.getPoint().x - n3.getPoint().x));
544
545 Length width = new Length(4.0, LengthUnit.METER);
546 dp1 = OtsGeometryUtil.offsetPoint(dp1, (-0.5 - laneOffsetAtStart) * width.getSI());
547 dp2 = OtsGeometryUtil.offsetPoint(dp2, (-0.5 - laneOffsetAtStart) * width.getSI());
548
549 OffsetCurve2d designLine = new BezierCubic2d(new Ray2d(dp1), new Ray2d(dp2), 0.5, false);
550 final CrossSectionLink link = makeLink(network, name, n2, n3,
551 designLine.toPolyLine(SEGMENTS).getPointList().toArray(new Point2d[65]), simulator);
552 Lane[] result = new Lane[laneCount];
553
554 for (int laneIndex = 0; laneIndex < laneCount; laneIndex++)
555 {
556
557
558
559 Length latPosAtStart = new Length(-laneIndex * width.getSI(), LengthUnit.SI);
560 Length latPosAtEnd = new Length(-laneIndex * width.getSI(), LengthUnit.SI);
561
562 ContinuousPiecewiseLinearFunction offsetFunc =
563 ContinuousPiecewiseLinearFunction.of(0.0, latPosAtStart.si, 1.0, latPosAtEnd.si);
564 ContinuousPiecewiseLinearFunction widthFunc = ContinuousPiecewiseLinearFunction.of(0.0, width.si, 1.0, width.si);
565 result[laneIndex] =
566 new Lane(link, "lane." + laneIndex, CrossSectionGeometry.of(designLine, SEGMENTS, offsetFunc, widthFunc),
567 laneType, Map.of(gtuType, speedLimit));
568 }
569 return result;
570 }
571
572
573
574
575
576
577
578
579
580 public static OtsLine2d makeBezier(final Node n1, final Node n2, final Node n3, final Node n4)
581 {
582 Point2d p1 = n1.getPoint();
583 Point2d p2 = n2.getPoint();
584 Point2d p3 = n3.getPoint();
585 Point2d p4 = n4.getPoint();
586 Ray2d dp1 = new Ray2d(p2.x, p2.y, Math.atan2(p2.y - p1.y, p2.x - p1.x));
587 Ray2d dp2 = new Ray2d(p3.x, p3.y, Math.atan2(p4.y - p3.y, p4.x - p3.x));
588
589 return new OtsLine2d(new BezierCubic2d(dp1, dp2).toPolyLine(SEGMENTS));
590 }
591 }