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