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