1 package org.opentrafficsim.road.network.lane;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.List;
7
8 import javax.media.j3d.Bounds;
9
10 import org.djunits.value.vdouble.scalar.Length;
11 import org.djutils.exceptions.Throw;
12 import org.opentrafficsim.base.Identifiable;
13 import org.opentrafficsim.core.animation.Drawable;
14 import org.opentrafficsim.core.geometry.Bezier;
15 import org.opentrafficsim.core.geometry.OTSGeometryException;
16 import org.opentrafficsim.core.geometry.OTSLine3D;
17 import org.opentrafficsim.core.geometry.OTSPoint3D;
18 import org.opentrafficsim.core.geometry.OTSShape;
19 import org.opentrafficsim.core.network.LateralDirectionality;
20 import org.opentrafficsim.core.network.NetworkException;
21 import org.opentrafficsim.road.network.RoadNetwork;
22
23 import nl.tudelft.simulation.dsol.animation.Locatable;
24 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
25 import nl.tudelft.simulation.event.EventProducer;
26 import nl.tudelft.simulation.language.d3.DirectedPoint;
27
28
29
30
31
32
33
34
35
36
37
38
39
40 public abstract class CrossSectionElement extends EventProducer implements Locatable, Serializable, Identifiable, Drawable
41 {
42
43 private static final long serialVersionUID = 20150826L;
44
45
46 private final String id;
47
48
49 @SuppressWarnings("checkstyle:visibilitymodifier")
50 protected final CrossSectionLink parentLink;
51
52
53 @SuppressWarnings("checkstyle:visibilitymodifier")
54 protected final List<CrossSectionSlice> crossSectionSlices;
55
56
57 @SuppressWarnings("checkstyle:visibilitymodifier")
58 protected final Length length;
59
60
61 private final OTSLine3D centerLine;
62
63
64 private final OTSShape contour;
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public CrossSectionElement(final CrossSectionLink parentLink, final String id,
80 final List<CrossSectionSlice> crossSectionSlices) throws OTSGeometryException, NetworkException
81 {
82 Throw.when(parentLink == null, NetworkException.class,
83 "Constructor of CrossSectionElement for id %s, parentLink cannot be null", id);
84 Throw.when(id == null, NetworkException.class, "Constructor of CrossSectionElement -- id cannot be null");
85 for (CrossSectionElement cse : parentLink.getCrossSectionElementList())
86 {
87 Throw.when(cse.getId().equals(id), NetworkException.class,
88 "Constructor of CrossSectionElement -- id %s not unique within the Link", id);
89 }
90 this.id = id;
91 this.parentLink = parentLink;
92
93 this.crossSectionSlices = new ArrayList<>(crossSectionSlices);
94 Throw.when(this.crossSectionSlices.size() == 0, NetworkException.class,
95 "CrossSectionElement %s is created with zero slices for %s", id, parentLink);
96 Throw.when(this.crossSectionSlices.get(0).getRelativeLength().si != 0.0, NetworkException.class,
97 "CrossSectionElement %s for %s has a first slice with relativeLength is not equal to 0.0", id, parentLink);
98 Throw.when(
99 this.crossSectionSlices.size() > 1 && this.crossSectionSlices.get(this.crossSectionSlices.size() - 1)
100 .getRelativeLength().ne(this.parentLink.getLength()),
101 NetworkException.class, "CrossSectionElement %s for %s has a last slice with relativeLength is not equal "
102 + "to the length of the parent link",
103 id, parentLink);
104
105 if (this.crossSectionSlices.size() <= 2)
106 {
107 this.centerLine = fixTightInnerCurve(new double[] { 0.0, 1.0 },
108 new double[] { getDesignLineOffsetAtBegin().getSI(), getDesignLineOffsetAtEnd().getSI() });
109 }
110 else
111 {
112 double[] fractions = new double[this.crossSectionSlices.size()];
113 double[] offsets = new double[this.crossSectionSlices.size()];
114 for (int i = 0; i < this.crossSectionSlices.size(); i++)
115 {
116 fractions[i] = this.crossSectionSlices.get(i).getRelativeLength().si / this.parentLink.getLength().si;
117 offsets[i] = this.crossSectionSlices.get(i).getDesignLineOffset().si;
118 }
119 this.centerLine = fixTightInnerCurve(fractions, offsets);
120 }
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138 this.length = this.centerLine.getLength();
139 this.contour = constructContour(this);
140
141 this.parentLink.addCrossSectionElement(this);
142 }
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158 public CrossSectionElement(final CrossSectionLink parentLink, final String id, final Length lateralOffsetAtBegin,
159 final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth)
160 throws OTSGeometryException, NetworkException
161 {
162 this(parentLink, id,
163 fixGradualLateraloffset(parentLink, lateralOffsetAtBegin, lateralOffsetAtEnd, beginWidth, endWidth));
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177 private static List<CrossSectionSlice> fixGradualLateraloffset(final CrossSectionLink parentLink,
178 final Length lateralOffsetAtBegin, final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth)
179 {
180 List<CrossSectionSlice> result = new ArrayList<>();
181 int numPoints = lateralOffsetAtBegin.equals(lateralOffsetAtEnd) ? 2 : 8;
182 Length parentLength = parentLink.getLength();
183 for (int index = 0; index < numPoints; index++)
184 {
185 double fraction = index * 1.0 / (numPoints - 1);
186 Length lengthAtCrossSection = parentLength.multiplyBy(fraction);
187 double relativeOffsetAtFraction = (1 + Math.sin((fraction - 0.5) * Math.PI)) / 2;
188 Length offsetAtFraction = Length.interpolate(lateralOffsetAtBegin, lateralOffsetAtEnd, relativeOffsetAtFraction);
189 result.add(new CrossSectionSlice(lengthAtCrossSection, offsetAtFraction,
190 Length.interpolate(beginWidth, endWidth, fraction)));
191
192
193
194 }
195
196
197
198 return result;
199 }
200
201
202
203
204
205
206
207
208
209
210 private OTSLine3D fixTightInnerCurve(final double[] fractions, final double[] offsets) throws OTSGeometryException
211 {
212 OTSLine3D linkCenterLine = getParentLink().getDesignLine();
213 for (int i = 1; i < linkCenterLine.size() - 1; i++)
214 {
215 double fraction = linkCenterLine.getVertexFraction(i);
216 int index = 0;
217 while (index < fractions.length - 2 && fraction > fractions[index + 1])
218 {
219 index++;
220 }
221 double w = (fraction - fractions[index]) / (fractions[index + 1] - fractions[index]);
222 double offset = (1.0 - w) * offsets[index] + w * offsets[index + 1];
223 double radius = linkCenterLine.getVertexRadius(i).si;
224 if ((radius < 0.0 && offset < 0.0 && offset < radius) || (radius > 0.0 && offset > 0.0 && offset > radius))
225 {
226 double offsetStart = getDesignLineOffsetAtBegin().getSI();
227 double offsetEnd = getDesignLineOffsetAtEnd().getSI();
228 DirectedPoint start = linkCenterLine.getLocationFraction(0.0);
229 DirectedPoint end = linkCenterLine.getLocationFraction(1.0);
230 start = new DirectedPoint(start.x - Math.sin(start.getRotZ()) * offsetStart,
231 start.y + Math.cos(start.getRotZ()) * offsetStart, start.z, start.getRotX(), start.getRotY(),
232 start.getRotZ());
233 end = new DirectedPoint(end.x - Math.sin(end.getRotZ()) * offsetEnd,
234 end.y + Math.cos(end.getRotZ()) * offsetEnd, end.z, end.getRotX(), end.getRotY(), end.getRotZ());
235 while (this.crossSectionSlices.size() > 2)
236 {
237 this.crossSectionSlices.remove(1);
238 }
239 return Bezier.cubic(start, end);
240 }
241 }
242 if (this.crossSectionSlices.size() <= 2)
243 {
244 return this.getParentLink().getDesignLine().offsetLine(getDesignLineOffsetAtBegin().getSI(),
245 getDesignLineOffsetAtEnd().getSI());
246 }
247 else
248 {
249 for (int i = 0; i < this.crossSectionSlices.size(); i++)
250 {
251 fractions[i] = this.crossSectionSlices.get(i).getRelativeLength().si / this.parentLink.getLength().si;
252 offsets[i] = this.crossSectionSlices.get(i).getDesignLineOffset().si;
253 }
254 return this.getParentLink().getDesignLine().offsetLine(fractions, offsets);
255 }
256 }
257
258
259
260
261
262
263
264
265
266 protected CrossSectionElement(final CrossSectionLink newCrossSectionLink,
267 final SimulatorInterface.TimeDoubleUnit newSimulator, final CrossSectionElement cse) throws NetworkException
268 {
269 this.id = cse.id;
270 this.parentLink = newCrossSectionLink;
271 this.centerLine = cse.centerLine;
272 this.length = this.centerLine.getLength();
273 this.contour = cse.contour;
274 this.crossSectionSlices = new ArrayList<>(cse.crossSectionSlices);
275 newCrossSectionLink.addCrossSectionElement(this);
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289 public CrossSectionElement(final CrossSectionLink parentLink, final String id, final Length lateralOffset,
290 final Length width) throws OTSGeometryException, NetworkException
291 {
292 this(parentLink, id,
293 Arrays.asList(new CrossSectionSlice[] { new CrossSectionSlice(Length.ZERO, lateralOffset, width) }));
294 }
295
296
297
298
299 public final CrossSectionLink getParentLink()
300 {
301 return this.parentLink;
302 }
303
304
305
306
307 public final RoadNetwork getNetwork()
308 {
309 return this.parentLink.getNetwork();
310 }
311
312
313
314
315
316
317 private int calculateSliceNumber(final double fractionalPosition)
318 {
319 double linkLength = this.parentLink.getLength().si;
320 for (int i = 0; i < this.crossSectionSlices.size() - 1; i++)
321 {
322 if (fractionalPosition >= this.crossSectionSlices.get(i).getRelativeLength().si / linkLength
323 && fractionalPosition <= this.crossSectionSlices.get(i + 1).getRelativeLength().si / linkLength)
324 {
325 return i;
326 }
327 }
328 return this.crossSectionSlices.size() - 2;
329 }
330
331
332
333
334
335
336 public final Length getLateralCenterPosition(final double fractionalPosition)
337 {
338 if (this.crossSectionSlices.size() == 1)
339 {
340 return this.getDesignLineOffsetAtBegin();
341 }
342 if (this.crossSectionSlices.size() == 2)
343 {
344 return Length.interpolate(this.getDesignLineOffsetAtBegin(), this.getDesignLineOffsetAtEnd(), fractionalPosition);
345 }
346 int sliceNr = calculateSliceNumber(fractionalPosition);
347 return Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(),
348 this.crossSectionSlices.get(sliceNr + 1).getDesignLineOffset(), fractionalPosition
349 - this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si);
350 }
351
352
353
354
355
356
357 public final Length getLateralCenterPosition(final Length longitudinalPosition)
358 {
359 return getLateralCenterPosition(longitudinalPosition.getSI() / getLength().getSI());
360 }
361
362
363
364
365
366
367 public final Length getWidth(final Length longitudinalPosition)
368 {
369 return getWidth(longitudinalPosition.getSI() / getLength().getSI());
370 }
371
372
373
374
375
376
377 public final Length getWidth(final double fractionalPosition)
378 {
379 if (this.crossSectionSlices.size() == 1)
380 {
381 return this.getBeginWidth();
382 }
383 if (this.crossSectionSlices.size() == 2)
384 {
385 return Length.interpolate(this.getBeginWidth(), this.getEndWidth(), fractionalPosition);
386 }
387 int sliceNr = calculateSliceNumber(fractionalPosition);
388 return Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
389 this.crossSectionSlices.get(sliceNr + 1).getWidth(), fractionalPosition
390 - this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si);
391 }
392
393
394
395
396
397 public final Length getLength()
398 {
399 return this.length;
400 }
401
402
403
404
405
406 public final Length getDesignLineOffsetAtBegin()
407 {
408 return this.crossSectionSlices.get(0).getDesignLineOffset();
409 }
410
411
412
413
414
415 public final Length getDesignLineOffsetAtEnd()
416 {
417 return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getDesignLineOffset();
418 }
419
420
421
422
423
424 public final Length getBeginWidth()
425 {
426 return this.crossSectionSlices.get(0).getWidth();
427 }
428
429
430
431
432
433 public final Length getEndWidth()
434 {
435 return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getWidth();
436 }
437
438
439
440
441
442 protected abstract double getZ();
443
444
445
446
447
448 public final OTSLine3D getCenterLine()
449 {
450 return this.centerLine;
451 }
452
453
454
455
456
457 public final OTSShape getContour()
458 {
459 return this.contour;
460 }
461
462
463
464
465
466 @Override
467 public final String getId()
468 {
469 return this.id;
470 }
471
472
473
474
475
476 public final String getFullId()
477 {
478 return getParentLink().getId() + "." + this.id;
479 }
480
481
482
483
484
485
486
487
488 public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
489 final double fractionalLongitudinalPosition)
490 {
491 Length designLineOffset;
492 Length halfWidth;
493 if (this.crossSectionSlices.size() <= 2)
494 {
495 designLineOffset = Length.interpolate(getDesignLineOffsetAtBegin(), getDesignLineOffsetAtEnd(),
496 fractionalLongitudinalPosition);
497 halfWidth = Length.interpolate(getBeginWidth(), getEndWidth(), fractionalLongitudinalPosition).multiplyBy(0.5);
498 }
499 else
500 {
501 int sliceNr = calculateSliceNumber(fractionalLongitudinalPosition);
502 double startFractionalPosition =
503 this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si;
504 designLineOffset = Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(),
505 this.crossSectionSlices.get(sliceNr + 1).getDesignLineOffset(),
506 fractionalLongitudinalPosition - startFractionalPosition);
507 halfWidth = Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
508 this.crossSectionSlices.get(sliceNr + 1).getWidth(),
509 fractionalLongitudinalPosition - startFractionalPosition).multiplyBy(0.5);
510 }
511
512 switch (lateralDirection)
513 {
514 case LEFT:
515 return designLineOffset.minus(halfWidth);
516 case RIGHT:
517 return designLineOffset.plus(halfWidth);
518 default:
519 throw new Error("Bad switch on LateralDirectionality " + lateralDirection);
520 }
521 }
522
523
524
525
526
527
528
529
530 public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
531 final Length longitudinalPosition)
532 {
533 return getLateralBoundaryPosition(lateralDirection, longitudinalPosition.getSI() / getLength().getSI());
534 }
535
536
537
538
539
540
541
542
543
544 public static OTSShape constructContour(final CrossSectionElement cse) throws OTSGeometryException, NetworkException
545 {
546 OTSPoint3D[] result = null;
547
548 if (cse.crossSectionSlices.size() <= 2)
549 {
550 OTSLine3D crossSectionDesignLine = cse.centerLine;
551 OTSLine3D rightBoundary =
552 crossSectionDesignLine.offsetLine(-cse.getBeginWidth().getSI() / 2, -cse.getEndWidth().getSI() / 2);
553 OTSLine3D leftBoundary =
554 crossSectionDesignLine.offsetLine(cse.getBeginWidth().getSI() / 2, cse.getEndWidth().getSI() / 2);
555 result = new OTSPoint3D[rightBoundary.size() + leftBoundary.size() + 1];
556 int resultIndex = 0;
557 for (int index = 0; index < rightBoundary.size(); index++)
558 {
559 result[resultIndex++] = rightBoundary.get(index);
560 }
561 for (int index = leftBoundary.size(); --index >= 0;)
562 {
563 result[resultIndex++] = leftBoundary.get(index);
564 }
565 result[resultIndex] = rightBoundary.get(0);
566 }
567 else
568 {
569 List<OTSPoint3D> resultList = new ArrayList<>();
570 List<OTSPoint3D> rightBoundary = new ArrayList<>();
571 for (int i = 0; i < cse.crossSectionSlices.size() - 1; i++)
572 {
573 double plLength = cse.getParentLink().getLength().si;
574 double so = cse.crossSectionSlices.get(i).getDesignLineOffset().si;
575 double eo = cse.crossSectionSlices.get(i + 1).getDesignLineOffset().si;
576 double sw2 = cse.crossSectionSlices.get(i).getWidth().si / 2.0;
577 double ew2 = cse.crossSectionSlices.get(i + 1).getWidth().si / 2.0;
578 double sf = cse.crossSectionSlices.get(i).getRelativeLength().si / plLength;
579 double ef = cse.crossSectionSlices.get(i + 1).getRelativeLength().si / plLength;
580 OTSLine3D crossSectionDesignLine =
581 cse.getParentLink().getDesignLine().extractFractional(sf, ef).offsetLine(so, eo);
582 resultList.addAll(Arrays.asList(crossSectionDesignLine.offsetLine(-sw2, -ew2).getPoints()));
583 rightBoundary.addAll(Arrays.asList(crossSectionDesignLine.offsetLine(sw2, ew2).getPoints()));
584 }
585 for (int index = rightBoundary.size(); --index >= 0;)
586 {
587 resultList.add(rightBoundary.get(index));
588 }
589
590 resultList.add(resultList.get(0));
591 result = resultList.toArray(new OTSPoint3D[] {});
592 }
593 return OTSShape.createAndCleanOTSShape(result);
594 }
595
596
597 @Override
598 @SuppressWarnings("checkstyle:designforextension")
599 public DirectedPoint getLocation()
600 {
601 DirectedPoint centroid = this.contour.getLocation();
602 return new DirectedPoint(centroid.x, centroid.y, getZ());
603 }
604
605
606 @Override
607 @SuppressWarnings("checkstyle:designforextension")
608 public Bounds getBounds()
609 {
610 return this.contour.getBounds();
611 }
612
613
614 @Override
615 @SuppressWarnings("checkstyle:designforextension")
616 public String toString()
617 {
618 return String.format("CSE offset %.2fm..%.2fm, width %.2fm..%.2fm", getDesignLineOffsetAtBegin().getSI(),
619 getDesignLineOffsetAtEnd().getSI(), getBeginWidth().getSI(), getEndWidth().getSI());
620 }
621
622
623 @SuppressWarnings("checkstyle:designforextension")
624 @Override
625 public int hashCode()
626 {
627 final int prime = 31;
628 int result = 1;
629 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
630 result = prime * result + ((this.parentLink == null) ? 0 : this.parentLink.hashCode());
631 return result;
632 }
633
634
635 @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
636 @Override
637 public boolean equals(final Object obj)
638 {
639 if (this == obj)
640 return true;
641 if (obj == null)
642 return false;
643 if (getClass() != obj.getClass())
644 return false;
645 CrossSectionElement other = (CrossSectionElement) obj;
646 if (this.id == null)
647 {
648 if (other.id != null)
649 return false;
650 }
651 else if (!this.id.equals(other.id))
652 return false;
653 if (this.parentLink == null)
654 {
655 if (other.parentLink != null)
656 return false;
657 }
658 else if (!this.parentLink.equals(other.parentLink))
659 return false;
660 return true;
661 }
662
663
664
665
666
667
668
669
670 @SuppressWarnings("checkstyle:designforextension")
671 public abstract CrossSectionElement clone(CrossSectionLink newParentLink, SimulatorInterface.TimeDoubleUnit newSimulator)
672 throws NetworkException;
673 }