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