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