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