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
49
50
51
52
53
54 public final class LaneFactory
55 {
56
57
58 private static final double BEZIER_MARGIN = Math.toRadians(0.5);
59
60
61 private static final NumSegments SEGMENTS = new NumSegments(64);
62
63
64 private final CrossSectionLink link;
65
66
67 private final ContinuousLine line;
68
69
70 private Length offset;
71
72
73 private Length laneWidth0;
74
75
76 private Length offsetStart = Length.ZERO;
77
78
79 private Length offsetEnd = Length.ZERO;
80
81
82 private LaneType laneType0;
83
84
85 private Speed speedLimit0;
86
87
88 private GtuType gtuType;
89
90
91 private final List<Lane> lanes = new ArrayList<>();
92
93
94 private Stripe firstStripe;
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 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
136
137
138
139
140
141 private static ContinuousLine makeLine(final Node from, final Node to)
142 {
143
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
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 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
196
197
198
199
200
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
223
224
225
226 public LaneFactory setOffsetStart(final Length startOffset)
227 {
228 this.offsetStart = startOffset;
229 return this;
230 }
231
232
233
234
235
236
237 public LaneFactory setOffsetEnd(final Length endOffset)
238 {
239 this.offsetEnd = endOffset;
240 return this;
241 }
242
243
244
245
246
247
248
249
250
251 public LaneFactory addLanes(final StripeData... types)
252 {
253 return addLanes(new ArrayList<>(), types);
254 }
255
256
257
258
259
260
261
262
263
264
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
301
302
303
304
305
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
355
356
357 public List<Lane> getLanes()
358 {
359 return this.lanes;
360 }
361
362
363
364
365
366
367
368
369
370
371
372
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
387
388
389
390
391
392
393
394
395
396
397
398
399
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
416
417
418
419
420
421
422
423
424
425
426
427
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
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
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
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
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 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
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
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
554
555
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
570
571
572
573
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 }