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