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