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 public final String getFullId()
371 {
372 return getParentLink().getId() + "." + this.id;
373 }
374
375
376
377
378
379
380
381
382 public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
383 final double fractionalLongitudinalPosition)
384 {
385 Length designLineOffset;
386 Length halfWidth;
387 if (this.crossSectionSlices.size() <= 2)
388 {
389 designLineOffset = Length.interpolate(getDesignLineOffsetAtBegin(), getDesignLineOffsetAtEnd(),
390 fractionalLongitudinalPosition);
391 halfWidth = Length.interpolate(getBeginWidth(), getEndWidth(), fractionalLongitudinalPosition).multiplyBy(0.5);
392 }
393 else
394 {
395 int sliceNr = calculateSliceNumber(fractionalLongitudinalPosition);
396 double startFractionalPosition =
397 this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si;
398 designLineOffset = Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(),
399 this.crossSectionSlices.get(sliceNr + 1).getDesignLineOffset(),
400 fractionalLongitudinalPosition - startFractionalPosition);
401 halfWidth = Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
402 this.crossSectionSlices.get(sliceNr + 1).getWidth(),
403 fractionalLongitudinalPosition - startFractionalPosition).multiplyBy(0.5);
404 }
405
406 switch (lateralDirection)
407 {
408 case LEFT:
409 return designLineOffset.minus(halfWidth);
410 case RIGHT:
411 return designLineOffset.plus(halfWidth);
412 default:
413 throw new Error("Bad switch on LateralDirectionality " + lateralDirection);
414 }
415 }
416
417
418
419
420
421
422
423
424 public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
425 final Length longitudinalPosition)
426 {
427 return getLateralBoundaryPosition(lateralDirection, longitudinalPosition.getSI() / getLength().getSI());
428 }
429
430
431
432
433
434
435
436
437
438 public static OTSShape constructContour(final CrossSectionElement cse) throws OTSGeometryException, NetworkException
439 {
440 OTSPoint3D[] result = null;
441
442 if (cse.crossSectionSlices.size() <= 2)
443 {
444 OTSLine3D crossSectionDesignLine = cse.getParentLink().getDesignLine()
445 .offsetLine(cse.getDesignLineOffsetAtBegin().getSI(), cse.getDesignLineOffsetAtEnd().getSI());
446 OTSLine3D rightBoundary =
447 crossSectionDesignLine.offsetLine(-cse.getBeginWidth().getSI() / 2, -cse.getEndWidth().getSI() / 2);
448 OTSLine3D leftBoundary =
449 crossSectionDesignLine.offsetLine(cse.getBeginWidth().getSI() / 2, cse.getEndWidth().getSI() / 2);
450 result = new OTSPoint3D[rightBoundary.size() + leftBoundary.size() + 1];
451 int resultIndex = 0;
452 for (int index = 0; index < rightBoundary.size(); index++)
453 {
454 result[resultIndex++] = rightBoundary.get(index);
455 }
456 for (int index = leftBoundary.size(); --index >= 0;)
457 {
458 result[resultIndex++] = leftBoundary.get(index);
459 }
460 result[resultIndex] = rightBoundary.get(0);
461 }
462
463 else
464
465 {
466 List<OTSPoint3D> resultList = new ArrayList<>();
467 for (int i = 0; i < cse.crossSectionSlices.size() - 1; i++)
468 {
469 double plLength = cse.getParentLink().getLength().si;
470 double so = cse.crossSectionSlices.get(i).getDesignLineOffset().si;
471 double eo = cse.crossSectionSlices.get(i + 1).getDesignLineOffset().si;
472 double sw2 = cse.crossSectionSlices.get(i).getWidth().si / 2.0;
473 double ew2 = cse.crossSectionSlices.get(i + 1).getWidth().si / 2.0;
474 double sf = cse.crossSectionSlices.get(i).getRelativeLength().si / plLength;
475 double ef = cse.crossSectionSlices.get(i + 1).getRelativeLength().si / plLength;
476 OTSLine3D crossSectionDesignLine =
477 cse.getParentLink().getDesignLine().extractFractional(sf, ef).offsetLine(so, eo);
478 OTSLine3D rightBoundary = crossSectionDesignLine.offsetLine(-sw2, -ew2);
479 OTSLine3D leftBoundary = crossSectionDesignLine.offsetLine(sw2, ew2);
480 for (int index = 0; index < rightBoundary.size(); index++)
481 {
482 resultList.add(rightBoundary.get(index));
483 }
484 for (int index = leftBoundary.size(); --index >= 0;)
485 {
486 resultList.add(leftBoundary.get(index));
487 }
488 }
489
490 resultList.add(resultList.get(0));
491 result = resultList.toArray(new OTSPoint3D[] {});
492 }
493
494 return OTSShape.createAndCleanOTSShape(result);
495 }
496
497
498 @Override
499 @SuppressWarnings("checkstyle:designforextension")
500 public DirectedPoint getLocation()
501 {
502 DirectedPoint centroid = this.contour.getLocation();
503 return new DirectedPoint(centroid.x, centroid.y, getZ());
504 }
505
506
507 @Override
508 @SuppressWarnings("checkstyle:designforextension")
509 public Bounds getBounds()
510 {
511 return this.contour.getBounds();
512 }
513
514
515 @Override
516 @SuppressWarnings("checkstyle:designforextension")
517 public String toString()
518 {
519 return String.format("CSE offset %.2fm..%.2fm, width %.2fm..%.2fm", getDesignLineOffsetAtBegin().getSI(),
520 getDesignLineOffsetAtEnd().getSI(), getBeginWidth().getSI(), getEndWidth().getSI());
521 }
522
523
524 @SuppressWarnings("checkstyle:designforextension")
525 @Override
526 public int hashCode()
527 {
528 final int prime = 31;
529 int result = 1;
530 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
531 result = prime * result + ((this.parentLink == null) ? 0 : this.parentLink.hashCode());
532 return result;
533 }
534
535
536 @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
537 @Override
538 public boolean equals(final Object obj)
539 {
540 if (this == obj)
541 return true;
542 if (obj == null)
543 return false;
544 if (getClass() != obj.getClass())
545 return false;
546 CrossSectionElement other = (CrossSectionElement) obj;
547 if (this.id == null)
548 {
549 if (other.id != null)
550 return false;
551 }
552 else if (!this.id.equals(other.id))
553 return false;
554 if (this.parentLink == null)
555 {
556 if (other.parentLink != null)
557 return false;
558 }
559 else if (!this.parentLink.equals(other.parentLink))
560 return false;
561 return true;
562 }
563
564
565
566
567
568
569
570
571
572 @SuppressWarnings("checkstyle:designforextension")
573 public abstract CrossSectionElement clone(final CrossSectionLink newParentLink, final OTSSimulatorInterface newSimulator,
574 final boolean animation) throws NetworkException;
575 }