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