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