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