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