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 + ", " + result.get(result.size() - 1));
180 }
181
182
183
184 return result;
185 }
186
187
188
189
190
191
192
193
194
195
196 protected CrossSectionElement(final CrossSectionLink newCrossSectionLink, final SimulatorInterface.TimeDoubleUnit newSimulator,
197 final boolean animation, final CrossSectionElement cse) throws NetworkException
198 {
199 this.id = cse.id;
200 this.parentLink = newCrossSectionLink;
201 this.centerLine = cse.centerLine;
202 this.length = this.centerLine.getLength();
203 this.contour = cse.contour;
204 this.crossSectionSlices = new ArrayList<>(cse.crossSectionSlices);
205 newCrossSectionLink.addCrossSectionElement(this);
206 }
207
208
209
210
211
212
213
214
215
216
217
218
219 public CrossSectionElement(final CrossSectionLink parentLink, final String id, final Length lateralOffset,
220 final Length width) throws OTSGeometryException, NetworkException
221 {
222 this(parentLink, id, Arrays
223 .asList(new CrossSectionSlice[] { new CrossSectionSlice(Length.ZERO, lateralOffset, width) }));
224 }
225
226
227
228
229 public final CrossSectionLink getParentLink()
230 {
231 return this.parentLink;
232 }
233
234
235
236
237
238
239 private int calculateSliceNumber(final double fractionalPosition)
240 {
241 double linkLength = this.parentLink.getLength().si;
242 for (int i = 0; i < this.crossSectionSlices.size() - 1; i++)
243 {
244 if (fractionalPosition >= this.crossSectionSlices.get(i).getRelativeLength().si / linkLength
245 && fractionalPosition <= this.crossSectionSlices.get(i + 1).getRelativeLength().si / linkLength)
246 {
247 return i;
248 }
249 }
250 return this.crossSectionSlices.size() - 2;
251 }
252
253
254
255
256
257
258 public final Length getLateralCenterPosition(final double fractionalPosition)
259 {
260 if (this.crossSectionSlices.size() == 1)
261 {
262 return this.getDesignLineOffsetAtBegin();
263 }
264 if (this.crossSectionSlices.size() == 2)
265 {
266 return Length
267 .interpolate(this.getDesignLineOffsetAtBegin(), this.getDesignLineOffsetAtEnd(), fractionalPosition);
268 }
269 int sliceNr = calculateSliceNumber(fractionalPosition);
270 return Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(),
271 this.crossSectionSlices.get(sliceNr + 1).getDesignLineOffset(), fractionalPosition
272 - this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si);
273 }
274
275
276
277
278
279
280 public final Length getLateralCenterPosition(final Length longitudinalPosition)
281 {
282 return getLateralCenterPosition(longitudinalPosition.getSI() / getLength().getSI());
283 }
284
285
286
287
288
289
290 public final Length getWidth(final Length longitudinalPosition)
291 {
292 return getWidth(longitudinalPosition.getSI() / getLength().getSI());
293 }
294
295
296
297
298
299
300 public final Length getWidth(final double fractionalPosition)
301 {
302 if (this.crossSectionSlices.size() == 1)
303 {
304 return this.getBeginWidth();
305 }
306 if (this.crossSectionSlices.size() == 2)
307 {
308 return Length.interpolate(this.getBeginWidth(), this.getEndWidth(), fractionalPosition);
309 }
310 int sliceNr = calculateSliceNumber(fractionalPosition);
311 return Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(), this.crossSectionSlices.get(sliceNr + 1)
312 .getWidth(), fractionalPosition - this.crossSectionSlices.get(sliceNr).getRelativeLength().si
313 / this.parentLink.getLength().si);
314 }
315
316
317
318
319
320 public final Length getLength()
321 {
322 return this.length;
323 }
324
325
326
327
328
329 public final Length getDesignLineOffsetAtBegin()
330 {
331 return this.crossSectionSlices.get(0).getDesignLineOffset();
332 }
333
334
335
336
337
338 public final Length getDesignLineOffsetAtEnd()
339 {
340 return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getDesignLineOffset();
341 }
342
343
344
345
346
347 public final Length getBeginWidth()
348 {
349 return this.crossSectionSlices.get(0).getWidth();
350 }
351
352
353
354
355
356 public final Length getEndWidth()
357 {
358 return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getWidth();
359 }
360
361
362
363
364
365 protected abstract double getZ();
366
367
368
369
370
371 public final OTSLine3D getCenterLine()
372 {
373 return this.centerLine;
374 }
375
376
377
378
379
380 public final OTSShape getContour()
381 {
382 return this.contour;
383 }
384
385
386
387
388
389 @Override
390 public final String getId()
391 {
392 return this.id;
393 }
394
395
396
397
398
399 public final String getFullId()
400 {
401 return getParentLink().getId() + "." + this.id;
402 }
403
404
405
406
407
408
409
410
411 public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
412 final double fractionalLongitudinalPosition)
413 {
414 Length designLineOffset;
415 Length halfWidth;
416 if (this.crossSectionSlices.size() <= 2)
417 {
418 designLineOffset =
419 Length.interpolate(getDesignLineOffsetAtBegin(), getDesignLineOffsetAtEnd(),
420 fractionalLongitudinalPosition);
421 halfWidth = Length.interpolate(getBeginWidth(), getEndWidth(), fractionalLongitudinalPosition).multiplyBy(0.5);
422 }
423 else
424 {
425 int sliceNr = calculateSliceNumber(fractionalLongitudinalPosition);
426 double startFractionalPosition =
427 this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si;
428 designLineOffset =
429 Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(), this.crossSectionSlices
430 .get(sliceNr + 1).getDesignLineOffset(), fractionalLongitudinalPosition
431 - startFractionalPosition);
432 halfWidth =
433 Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
434 this.crossSectionSlices.get(sliceNr + 1).getWidth(),
435 fractionalLongitudinalPosition - startFractionalPosition).multiplyBy(0.5);
436 }
437
438 switch (lateralDirection)
439 {
440 case LEFT:
441 return designLineOffset.minus(halfWidth);
442 case RIGHT:
443 return designLineOffset.plus(halfWidth);
444 default:
445 throw new Error("Bad switch on LateralDirectionality " + lateralDirection);
446 }
447 }
448
449
450
451
452
453
454
455
456 public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
457 final Length longitudinalPosition)
458 {
459 return getLateralBoundaryPosition(lateralDirection, longitudinalPosition.getSI() / getLength().getSI());
460 }
461
462
463
464
465
466
467
468
469
470 public static OTSShape constructContour(final CrossSectionElement cse) throws OTSGeometryException, NetworkException
471 {
472 OTSPoint3D[] result = null;
473
474 if (cse.crossSectionSlices.size() <= 2)
475 {
476 OTSLine3D crossSectionDesignLine =
477 cse.getParentLink().getDesignLine()
478 .offsetLine(cse.getDesignLineOffsetAtBegin().getSI(), cse.getDesignLineOffsetAtEnd().getSI());
479 OTSLine3D rightBoundary =
480 crossSectionDesignLine.offsetLine(-cse.getBeginWidth().getSI() / 2, -cse.getEndWidth().getSI() / 2);
481 OTSLine3D leftBoundary =
482 crossSectionDesignLine.offsetLine(cse.getBeginWidth().getSI() / 2, cse.getEndWidth().getSI() / 2);
483 result = new OTSPoint3D[rightBoundary.size() + leftBoundary.size() + 1];
484 int resultIndex = 0;
485 for (int index = 0; index < rightBoundary.size(); index++)
486 {
487 result[resultIndex++] = rightBoundary.get(index);
488 }
489 for (int index = leftBoundary.size(); --index >= 0;)
490 {
491 result[resultIndex++] = leftBoundary.get(index);
492 }
493 result[resultIndex] = rightBoundary.get(0);
494 }
495 else
496 {
497 List<OTSPoint3D> resultList = new ArrayList<>();
498 List<OTSPoint3D> rightBoundary = new ArrayList<>();
499 for (int i = 0; i < cse.crossSectionSlices.size() - 1; i++)
500 {
501 double plLength = cse.getParentLink().getLength().si;
502 double so = cse.crossSectionSlices.get(i).getDesignLineOffset().si;
503 double eo = cse.crossSectionSlices.get(i + 1).getDesignLineOffset().si;
504 double sw2 = cse.crossSectionSlices.get(i).getWidth().si / 2.0;
505 double ew2 = cse.crossSectionSlices.get(i + 1).getWidth().si / 2.0;
506 double sf = cse.crossSectionSlices.get(i).getRelativeLength().si / plLength;
507 double ef = cse.crossSectionSlices.get(i + 1).getRelativeLength().si / plLength;
508 OTSLine3D crossSectionDesignLine =
509 cse.getParentLink().getDesignLine().extractFractional(sf, ef).offsetLine(so, eo);
510 resultList.addAll(Arrays.asList(crossSectionDesignLine.offsetLine(-sw2, -ew2).getPoints()));
511 rightBoundary.addAll(Arrays.asList(crossSectionDesignLine.offsetLine(sw2, ew2).getPoints()));
512 }
513 for (int index = rightBoundary.size(); --index >= 0;)
514 {
515 resultList.add(rightBoundary.get(index));
516 }
517
518 resultList.add(resultList.get(0));
519 result = resultList.toArray(new OTSPoint3D[] {});
520 }
521 return OTSShape.createAndCleanOTSShape(result);
522 }
523
524
525 @Override
526 @SuppressWarnings("checkstyle:designforextension")
527 public DirectedPoint getLocation()
528 {
529 DirectedPoint centroid = this.contour.getLocation();
530 return new DirectedPoint(centroid.x, centroid.y, getZ());
531 }
532
533
534 @Override
535 @SuppressWarnings("checkstyle:designforextension")
536 public Bounds getBounds()
537 {
538 return this.contour.getBounds();
539 }
540
541
542 @Override
543 @SuppressWarnings("checkstyle:designforextension")
544 public String toString()
545 {
546 return String.format("CSE offset %.2fm..%.2fm, width %.2fm..%.2fm", getDesignLineOffsetAtBegin().getSI(),
547 getDesignLineOffsetAtEnd().getSI(), getBeginWidth().getSI(), getEndWidth().getSI());
548 }
549
550
551 @SuppressWarnings("checkstyle:designforextension")
552 @Override
553 public int hashCode()
554 {
555 final int prime = 31;
556 int result = 1;
557 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
558 result = prime * result + ((this.parentLink == null) ? 0 : this.parentLink.hashCode());
559 return result;
560 }
561
562
563 @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
564 @Override
565 public boolean equals(final Object obj)
566 {
567 if (this == obj)
568 return true;
569 if (obj == null)
570 return false;
571 if (getClass() != obj.getClass())
572 return false;
573 CrossSectionElement other = (CrossSectionElement) obj;
574 if (this.id == null)
575 {
576 if (other.id != null)
577 return false;
578 }
579 else if (!this.id.equals(other.id))
580 return false;
581 if (this.parentLink == null)
582 {
583 if (other.parentLink != null)
584 return false;
585 }
586 else if (!this.parentLink.equals(other.parentLink))
587 return false;
588 return true;
589 }
590
591
592
593
594
595
596
597
598
599 @SuppressWarnings("checkstyle:designforextension")
600 public abstract CrossSectionElement clone(final CrossSectionLink newParentLink,
601 final SimulatorInterface.TimeDoubleUnit newSimulator, final boolean animation) throws NetworkException;
602 }