View Javadoc
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.Bezier;
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.OTSShape;
19  import org.opentrafficsim.core.network.LateralDirectionality;
20  import org.opentrafficsim.core.network.NetworkException;
21  import org.opentrafficsim.road.network.RoadNetwork;
22  
23  import nl.tudelft.simulation.dsol.animation.Locatable;
24  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
25  import nl.tudelft.simulation.event.EventProducer;
26  import nl.tudelft.simulation.language.d3.DirectedPoint;
27  
28  /**
29   * Cross section elements are used to compose a CrossSectionLink.
30   * <p>
31   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
33   * <p>
34   * $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
35   * initial version Aug 19, 2014 <br>
36   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
37   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
38   * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
39   */
40  public abstract class CrossSectionElement extends EventProducer implements Locatable, Serializable, Identifiable, Drawable
41  {
42      /** */
43      private static final long serialVersionUID = 20150826L;
44  
45      /** The id. Should be unique within the parentLink. */
46      private final String id;
47  
48      /** Cross Section Link to which the element belongs. */
49      @SuppressWarnings("checkstyle:visibilitymodifier")
50      protected final CrossSectionLink parentLink;
51  
52      /** The offsets and widths at positions along the line, relative to the design line of the parent link. */
53      @SuppressWarnings("checkstyle:visibilitymodifier")
54      protected final List<CrossSectionSlice> crossSectionSlices;
55  
56      /** The length of the line. Calculated once at the creation. */
57      @SuppressWarnings("checkstyle:visibilitymodifier")
58      protected final Length length;
59  
60      /** The center line of the element. Calculated once at the creation. */
61      private final OTSLine3D centerLine;
62  
63      /** The contour of the element. Calculated once at the creation. */
64      private final OTSShape contour;
65  
66      /**
67       * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction, with the direction from
68       * the StartNode towards the EndNode as the longitudinal direction.
69       * @param id String; The id of the CrosssSectionElement. Should be unique within the parentLink.
70       * @param parentLink CrossSectionLink; Link to which the element belongs.
71       * @param crossSectionSlices List&lt;CrossSectionSlice&gt;; the offsets and widths at positions along the line, relative to
72       *            the design line of the parent link. If there is just one with and offset, there should just be one element in
73       *            the list with Length = 0. If there are more slices, the last one should be at the length of the design line.
74       *            If not, a NetworkException is thrown.
75       * @throws OTSGeometryException when creation of the geometry fails
76       * @throws NetworkException when id equal to null or not unique, or there are multiple slices and the last slice does not
77       *             end at the length of the design line.
78       */
79      public CrossSectionElement(final CrossSectionLink parentLink, final String id,
80              final List<CrossSectionSlice> crossSectionSlices) throws OTSGeometryException, NetworkException
81      {
82          Throw.when(parentLink == null, NetworkException.class,
83                  "Constructor of CrossSectionElement for id %s, parentLink cannot be null", id);
84          Throw.when(id == null, NetworkException.class, "Constructor of CrossSectionElement -- id cannot be null");
85          for (CrossSectionElement cse : parentLink.getCrossSectionElementList())
86          {
87              Throw.when(cse.getId().equals(id), NetworkException.class,
88                      "Constructor of CrossSectionElement -- id %s not unique within the Link", id);
89          }
90          this.id = id;
91          this.parentLink = parentLink;
92  
93          this.crossSectionSlices = new ArrayList<>(crossSectionSlices); // copy of list with immutable slices
94          Throw.when(this.crossSectionSlices.size() == 0, NetworkException.class,
95                  "CrossSectionElement %s is created with zero slices for %s", id, parentLink);
96          Throw.when(this.crossSectionSlices.get(0).getRelativeLength().si != 0.0, NetworkException.class,
97                  "CrossSectionElement %s for %s has a first slice with relativeLength is not equal to 0.0", id, parentLink);
98          Throw.when(
99                  this.crossSectionSlices.size() > 1 && this.crossSectionSlices.get(this.crossSectionSlices.size() - 1)
100                         .getRelativeLength().ne(this.parentLink.getLength()),
101                 NetworkException.class, "CrossSectionElement %s for %s has a last slice with relativeLength is not equal "
102                         + "to the length of the parent link",
103                 id, parentLink);
104 
105         if (this.crossSectionSlices.size() <= 2)
106         {
107             this.centerLine = fixTightInnerCurve(new double[] { 0.0, 1.0 },
108                     new double[] { getDesignLineOffsetAtBegin().getSI(), getDesignLineOffsetAtEnd().getSI() });
109         }
110         else
111         {
112             double[] fractions = new double[this.crossSectionSlices.size()];
113             double[] offsets = new double[this.crossSectionSlices.size()];
114             for (int i = 0; i < this.crossSectionSlices.size(); i++)
115             {
116                 fractions[i] = this.crossSectionSlices.get(i).getRelativeLength().si / this.parentLink.getLength().si;
117                 offsets[i] = this.crossSectionSlices.get(i).getDesignLineOffset().si;
118             }
119             this.centerLine = fixTightInnerCurve(fractions, offsets);
120         }
121         // if (this.crossSectionSlices.size() <= 2)
122         // {
123         // this.centerLine = this.getParentLink().getDesignLine().offsetLine(getDesignLineOffsetAtBegin().getSI(),
124         // getDesignLineOffsetAtEnd().getSI());
125         // }
126         // else
127         // {
128         // double[] relativeFractions = new double[this.crossSectionSlices.size()];
129         // double[] offsets = new double[this.crossSectionSlices.size()];
130         // for (int i = 0; i < this.crossSectionSlices.size(); i++)
131         // {
132         // relativeFractions[i] = this.crossSectionSlices.get(i).getRelativeLength().si / this.parentLink.getLength().si;
133         // offsets[i] = this.crossSectionSlices.get(i).getDesignLineOffset().si;
134         // }
135         // this.centerLine = this.getParentLink().getDesignLine().offsetLine(relativeFractions, offsets);
136         // }
137 
138         this.length = this.centerLine.getLength();
139         this.contour = constructContour(this);
140 
141         this.parentLink.addCrossSectionElement(this);
142     }
143 
144     /**
145      * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction, with the direction from
146      * the StartNode towards the EndNode as the longitudinal direction.
147      * @param id String; The id of the CrosssSectionElement. Should be unique within the parentLink.
148      * @param parentLink CrossSectionLink; Link to which the element belongs.
149      * @param lateralOffsetAtBegin Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
150      *            design line of the parent Link at the start of the parent Link
151      * @param lateralOffsetAtEnd Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
152      *            design line of the parent Link at the end of the parent Link
153      * @param beginWidth Length; width at start, positioned <i>symmetrically around</i> the design line
154      * @param endWidth Length; width at end, positioned <i>symmetrically around</i> the design line
155      * @throws OTSGeometryException when creation of the geometry fails
156      * @throws NetworkException when id equal to null or not unique
157      */
158     public CrossSectionElement(final CrossSectionLink parentLink, final String id, final Length lateralOffsetAtBegin,
159             final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth)
160             throws OTSGeometryException, NetworkException
161     {
162         this(parentLink, id,
163                 fixGradualLateraloffset(parentLink, lateralOffsetAtBegin, lateralOffsetAtEnd, beginWidth, endWidth));
164     }
165 
166     /**
167      * Construct a list of cross section slices. using sinusoidal interpolation for changin lateral offset.
168      * @param parentLink CrossSectionLink; Link to which the element belongs.
169      * @param lateralOffsetAtBegin Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
170      *            design line of the parent Link at the start of the parent Link
171      * @param lateralOffsetAtEnd Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
172      *            design line of the parent Link at the end of the parent Link
173      * @param beginWidth Length; width at start, positioned <i>symmetrically around</i> the design line
174      * @param endWidth Length; width at end, positioned <i>symmetrically around</i> the design line
175      * @return List&ltCrossSectionSlice&gt;; the cross section slices
176      */
177     private static List<CrossSectionSlice> fixGradualLateraloffset(final CrossSectionLink parentLink,
178             final Length lateralOffsetAtBegin, final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth)
179     {
180         List<CrossSectionSlice> result = new ArrayList<>();
181         int numPoints = lateralOffsetAtBegin.equals(lateralOffsetAtEnd) ? 2 : 8;
182         Length parentLength = parentLink.getLength();
183         for (int index = 0; index < numPoints; index++)
184         {
185             double fraction = index * 1.0 / (numPoints - 1);
186             Length lengthAtCrossSection = parentLength.multiplyBy(fraction);
187             double relativeOffsetAtFraction = (1 + Math.sin((fraction - 0.5) * Math.PI)) / 2;
188             Length offsetAtFraction = Length.interpolate(lateralOffsetAtBegin, lateralOffsetAtEnd, relativeOffsetAtFraction);
189             result.add(new CrossSectionSlice(lengthAtCrossSection, offsetAtFraction,
190                     Length.interpolate(beginWidth, endWidth, fraction)));
191             // System.out.println("fraction " + fraction + ", angle " + Math.toDegrees((fraction - 0.5) * Math.PI) + ", fraction
192             // "
193             // + relativeOffsetAtFraction + ", " + result.get(result.size() - 1));
194         }
195 
196         // Arrays.asList(new CrossSectionSlice[] { new CrossSectionSlice(Length.ZERO, lateralOffsetAtBegin, beginWidth),
197         // new CrossSectionSlice(parentLink.getLength(), lateralOffsetAtEnd, endWidth) })
198         return result;
199     }
200 
201     /**
202      * Returns the center line for this cross section element by adhering to the given offsets relative to the link design line.
203      * This method will create a Bezier curve, ignoring the link design line, if the offset at any vertex is larger than the
204      * radius, and on the inside of the curve.
205      * @param fractions double[]; length fractions of offsets
206      * @param offsets double[]; offsets
207      * @return OTSPoint3D; center line
208      * @throws OTSGeometryException index out of bounds
209      */
210     private OTSLine3D fixTightInnerCurve(final double[] fractions, final double[] offsets) throws OTSGeometryException
211     {
212         OTSLine3D linkCenterLine = getParentLink().getDesignLine();
213         for (int i = 1; i < linkCenterLine.size() - 1; i++)
214         {
215             double fraction = linkCenterLine.getVertexFraction(i);
216             int index = 0;
217             while (index < fractions.length - 2 && fraction > fractions[index + 1])
218             {
219                 index++;
220             }
221             double w = (fraction - fractions[index]) / (fractions[index + 1] - fractions[index]);
222             double offset = (1.0 - w) * offsets[index] + w * offsets[index + 1];
223             double radius = linkCenterLine.getVertexRadius(i).si;
224             if ((radius < 0.0 && offset < 0.0 && offset < radius) || (radius > 0.0 && offset > 0.0 && offset > radius))
225             {
226                 double offsetStart = getDesignLineOffsetAtBegin().getSI();
227                 double offsetEnd = getDesignLineOffsetAtEnd().getSI();
228                 DirectedPoint start = linkCenterLine.getLocationFraction(0.0);
229                 DirectedPoint end = linkCenterLine.getLocationFraction(1.0);
230                 start = new DirectedPoint(start.x - Math.sin(start.getRotZ()) * offsetStart,
231                         start.y + Math.cos(start.getRotZ()) * offsetStart, start.z, start.getRotX(), start.getRotY(),
232                         start.getRotZ());
233                 end = new DirectedPoint(end.x - Math.sin(end.getRotZ()) * offsetEnd,
234                         end.y + Math.cos(end.getRotZ()) * offsetEnd, end.z, end.getRotX(), end.getRotY(), end.getRotZ());
235                 while (this.crossSectionSlices.size() > 2)
236                 {
237                     this.crossSectionSlices.remove(1);
238                 }
239                 return Bezier.cubic(start, end);
240             }
241         }
242         if (this.crossSectionSlices.size() <= 2)
243         {
244             return this.getParentLink().getDesignLine().offsetLine(getDesignLineOffsetAtBegin().getSI(),
245                     getDesignLineOffsetAtEnd().getSI());
246         }
247         else
248         {
249             for (int i = 0; i < this.crossSectionSlices.size(); i++)
250             {
251                 fractions[i] = this.crossSectionSlices.get(i).getRelativeLength().si / this.parentLink.getLength().si;
252                 offsets[i] = this.crossSectionSlices.get(i).getDesignLineOffset().si;
253             }
254             return this.getParentLink().getDesignLine().offsetLine(fractions, offsets);
255         }
256     }
257 
258     /**
259      * Clone a CrossSectionElement for a new network.
260      * @param newCrossSectionLink CrossSectionLink; the new link to which the clone belongs
261      * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator for this network
262      * @param cse CrossSectionElement; the element to clone from
263      * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
264      *             or the end node of the link are not registered in the network.
265      */
266     protected CrossSectionElement(final CrossSectionLink newCrossSectionLink,
267             final SimulatorInterface.TimeDoubleUnit newSimulator, final CrossSectionElement cse) throws NetworkException
268     {
269         this.id = cse.id;
270         this.parentLink = newCrossSectionLink;
271         this.centerLine = cse.centerLine;
272         this.length = this.centerLine.getLength();
273         this.contour = cse.contour;
274         this.crossSectionSlices = new ArrayList<>(cse.crossSectionSlices);
275         newCrossSectionLink.addCrossSectionElement(this);
276     }
277 
278     /**
279      * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction, with the direction from
280      * the StartNode towards the EndNode as the longitudinal direction.
281      * @param id String; The id of the CrosssSectionElement. Should be unique within the parentLink.
282      * @param parentLink CrossSectionLink; Link to which the element belongs.
283      * @param lateralOffset Length; the lateral offset of the design line of the new CrossSectionLink with respect to the design
284      *            line of the parent Link
285      * @param width Length; width, positioned <i>symmetrically around</i> the design line
286      * @throws OTSGeometryException when creation of the geometry fails
287      * @throws NetworkException when id equal to null or not unique
288      */
289     public CrossSectionElement(final CrossSectionLink parentLink, final String id, final Length lateralOffset,
290             final Length width) throws OTSGeometryException, NetworkException
291     {
292         this(parentLink, id,
293                 Arrays.asList(new CrossSectionSlice[] { new CrossSectionSlice(Length.ZERO, lateralOffset, width) }));
294     }
295 
296     /**
297      * @return parentLink.
298      */
299     public final CrossSectionLink getParentLink()
300     {
301         return this.parentLink;
302     }
303 
304     /**
305      * @return the road network to which the lane belongs
306      */
307     public final RoadNetwork getNetwork()
308     {
309         return this.parentLink.getNetwork();
310     }
311 
312     /**
313      * Calculate the slice the fractional position is in.
314      * @param fractionalPosition double; the fractional position between 0 and 1 compared to the design line
315      * @return int; the lower slice number between 0 and number of slices - 1.
316      */
317     private int calculateSliceNumber(final double fractionalPosition)
318     {
319         double linkLength = this.parentLink.getLength().si;
320         for (int i = 0; i < this.crossSectionSlices.size() - 1; i++)
321         {
322             if (fractionalPosition >= this.crossSectionSlices.get(i).getRelativeLength().si / linkLength
323                     && fractionalPosition <= this.crossSectionSlices.get(i + 1).getRelativeLength().si / linkLength)
324             {
325                 return i;
326             }
327         }
328         return this.crossSectionSlices.size() - 2;
329     }
330 
331     /**
332      * Retrieve the lateral offset from the Link design line at the specified longitudinal position.
333      * @param fractionalPosition double; fractional longitudinal position on this Lane
334      * @return Length; the lateralCenterPosition at the specified longitudinal position
335      */
336     public final Length getLateralCenterPosition(final double fractionalPosition)
337     {
338         if (this.crossSectionSlices.size() == 1)
339         {
340             return this.getDesignLineOffsetAtBegin();
341         }
342         if (this.crossSectionSlices.size() == 2)
343         {
344             return Length.interpolate(this.getDesignLineOffsetAtBegin(), this.getDesignLineOffsetAtEnd(), fractionalPosition);
345         }
346         int sliceNr = calculateSliceNumber(fractionalPosition);
347         return Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(),
348                 this.crossSectionSlices.get(sliceNr + 1).getDesignLineOffset(), fractionalPosition
349                         - this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si);
350     }
351 
352     /**
353      * Retrieve the lateral offset from the Link design line at the specified longitudinal position.
354      * @param longitudinalPosition Length; the longitudinal position on this Lane
355      * @return Length; the lateralCenterPosition at the specified longitudinal position
356      */
357     public final Length getLateralCenterPosition(final Length longitudinalPosition)
358     {
359         return getLateralCenterPosition(longitudinalPosition.getSI() / getLength().getSI());
360     }
361 
362     /**
363      * Return the width of this CrossSectionElement at a specified longitudinal position.
364      * @param longitudinalPosition Length; the longitudinal position
365      * @return Length; the width of this CrossSectionElement at the specified longitudinal position.
366      */
367     public final Length getWidth(final Length longitudinalPosition)
368     {
369         return getWidth(longitudinalPosition.getSI() / getLength().getSI());
370     }
371 
372     /**
373      * Return the width of this CrossSectionElement at a specified fractional longitudinal position.
374      * @param fractionalPosition double; the fractional longitudinal position
375      * @return Length; the width of this CrossSectionElement at the specified fractional longitudinal position.
376      */
377     public final Length getWidth(final double fractionalPosition)
378     {
379         if (this.crossSectionSlices.size() == 1)
380         {
381             return this.getBeginWidth();
382         }
383         if (this.crossSectionSlices.size() == 2)
384         {
385             return Length.interpolate(this.getBeginWidth(), this.getEndWidth(), fractionalPosition);
386         }
387         int sliceNr = calculateSliceNumber(fractionalPosition);
388         return Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
389                 this.crossSectionSlices.get(sliceNr + 1).getWidth(), fractionalPosition
390                         - this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si);
391     }
392 
393     /**
394      * Return the length of this CrossSectionElement as measured along the design line (which equals the center line).
395      * @return Length; the length of this CrossSectionElement
396      */
397     public final Length getLength()
398     {
399         return this.length;
400     }
401 
402     /**
403      * Retrieve the offset from the design line at the begin of the parent link.
404      * @return Length; the offset of this CrossSectionElement at the begin of the parent link
405      */
406     public final Length getDesignLineOffsetAtBegin()
407     {
408         return this.crossSectionSlices.get(0).getDesignLineOffset();
409     }
410 
411     /**
412      * Retrieve the offset from the design line at the end of the parent link.
413      * @return Length; the offset of this CrossSectionElement at the end of the parent link
414      */
415     public final Length getDesignLineOffsetAtEnd()
416     {
417         return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getDesignLineOffset();
418     }
419 
420     /**
421      * Retrieve the width at the begin of the parent link.
422      * @return Length; the width of this CrossSectionElement at the begin of the parent link
423      */
424     public final Length getBeginWidth()
425     {
426         return this.crossSectionSlices.get(0).getWidth();
427     }
428 
429     /**
430      * Retrieve the width at the end of the parent link.
431      * @return Length; the width of this CrossSectionElement at the end of the parent link
432      */
433     public final Length getEndWidth()
434     {
435         return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getWidth();
436     }
437 
438     /**
439      * Retrieve the Z offset (used to determine what covers what when drawing).
440      * @return double; the Z-offset for drawing (what's on top, what's underneath).
441      */
442     protected abstract double getZ();
443 
444     /**
445      * Retrieve the center line of this CrossSectionElement.
446      * @return OTSLine3D; the center line of this CrossSectionElement
447      */
448     public final OTSLine3D getCenterLine()
449     {
450         return this.centerLine;
451     }
452 
453     /**
454      * Retrieve the contour of this CrossSectionElement.
455      * @return OTSShape; the contour of this CrossSectionElement
456      */
457     public final OTSShape getContour()
458     {
459         return this.contour;
460     }
461 
462     /**
463      * Retrieve the id of this CrossSectionElement.
464      * @return String; the id of this CrossSectionElement
465      */
466     @Override
467     public final String getId()
468     {
469         return this.id;
470     }
471 
472     /**
473      * Retrieve the id of this CrossSectionElement.
474      * @return String; the id of this CrossSectionElement
475      */
476     public final String getFullId()
477     {
478         return getParentLink().getId() + "." + this.id;
479     }
480 
481     /**
482      * Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
483      * CrossSectionElement at the specified fractional longitudinal position.
484      * @param lateralDirection LateralDirectionality; LEFT, or RIGHT
485      * @param fractionalLongitudinalPosition double; ranges from 0.0 (begin of parentLink) to 1.0 (end of parentLink)
486      * @return Length
487      */
488     public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
489             final double fractionalLongitudinalPosition)
490     {
491         Length designLineOffset;
492         Length halfWidth;
493         if (this.crossSectionSlices.size() <= 2)
494         {
495             designLineOffset = Length.interpolate(getDesignLineOffsetAtBegin(), getDesignLineOffsetAtEnd(),
496                     fractionalLongitudinalPosition);
497             halfWidth = Length.interpolate(getBeginWidth(), getEndWidth(), fractionalLongitudinalPosition).multiplyBy(0.5);
498         }
499         else
500         {
501             int sliceNr = calculateSliceNumber(fractionalLongitudinalPosition);
502             double startFractionalPosition =
503                     this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si;
504             designLineOffset = Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(),
505                     this.crossSectionSlices.get(sliceNr + 1).getDesignLineOffset(),
506                     fractionalLongitudinalPosition - startFractionalPosition);
507             halfWidth = Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
508                     this.crossSectionSlices.get(sliceNr + 1).getWidth(),
509                     fractionalLongitudinalPosition - startFractionalPosition).multiplyBy(0.5);
510         }
511 
512         switch (lateralDirection)
513         {
514             case LEFT:
515                 return designLineOffset.minus(halfWidth);
516             case RIGHT:
517                 return designLineOffset.plus(halfWidth);
518             default:
519                 throw new Error("Bad switch on LateralDirectionality " + lateralDirection);
520         }
521     }
522 
523     /**
524      * Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
525      * CrossSectionElement at the specified longitudinal position.
526      * @param lateralDirection LateralDirectionality; LEFT, or RIGHT
527      * @param longitudinalPosition Length; the position along the length of this CrossSectionElement
528      * @return Length
529      */
530     public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
531             final Length longitudinalPosition)
532     {
533         return getLateralBoundaryPosition(lateralDirection, longitudinalPosition.getSI() / getLength().getSI());
534     }
535 
536     /**
537      * Construct a buffer geometry by offsetting the linear geometry line with a distance and constructing a so-called "buffer"
538      * around it.
539      * @param cse CrossSectionElement; the cross section element to construct the contour for
540      * @return OTSShape; the geometry belonging to this CrossSectionElement.
541      * @throws OTSGeometryException when construction of the geometry fails
542      * @throws NetworkException when the resulting contour is degenerate (cannot happen; we hope)
543      */
544     public static OTSShape constructContour(final CrossSectionElement cse) throws OTSGeometryException, NetworkException
545     {
546         OTSPoint3D[] result = null;
547 
548         if (cse.crossSectionSlices.size() <= 2)
549         {
550             OTSLine3D crossSectionDesignLine = cse.centerLine;
551             OTSLine3D rightBoundary =
552                     crossSectionDesignLine.offsetLine(-cse.getBeginWidth().getSI() / 2, -cse.getEndWidth().getSI() / 2);
553             OTSLine3D leftBoundary =
554                     crossSectionDesignLine.offsetLine(cse.getBeginWidth().getSI() / 2, cse.getEndWidth().getSI() / 2);
555             result = new OTSPoint3D[rightBoundary.size() + leftBoundary.size() + 1];
556             int resultIndex = 0;
557             for (int index = 0; index < rightBoundary.size(); index++)
558             {
559                 result[resultIndex++] = rightBoundary.get(index);
560             }
561             for (int index = leftBoundary.size(); --index >= 0;)
562             {
563                 result[resultIndex++] = leftBoundary.get(index);
564             }
565             result[resultIndex] = rightBoundary.get(0); // close the contour
566         }
567         else
568         {
569             List<OTSPoint3D> resultList = new ArrayList<>();
570             List<OTSPoint3D> rightBoundary = new ArrayList<>();
571             for (int i = 0; i < cse.crossSectionSlices.size() - 1; i++)
572             {
573                 double plLength = cse.getParentLink().getLength().si;
574                 double so = cse.crossSectionSlices.get(i).getDesignLineOffset().si;
575                 double eo = cse.crossSectionSlices.get(i + 1).getDesignLineOffset().si;
576                 double sw2 = cse.crossSectionSlices.get(i).getWidth().si / 2.0;
577                 double ew2 = cse.crossSectionSlices.get(i + 1).getWidth().si / 2.0;
578                 double sf = cse.crossSectionSlices.get(i).getRelativeLength().si / plLength;
579                 double ef = cse.crossSectionSlices.get(i + 1).getRelativeLength().si / plLength;
580                 OTSLine3D crossSectionDesignLine =
581                         cse.getParentLink().getDesignLine().extractFractional(sf, ef).offsetLine(so, eo);
582                 resultList.addAll(Arrays.asList(crossSectionDesignLine.offsetLine(-sw2, -ew2).getPoints()));
583                 rightBoundary.addAll(Arrays.asList(crossSectionDesignLine.offsetLine(sw2, ew2).getPoints()));
584             }
585             for (int index = rightBoundary.size(); --index >= 0;)
586             {
587                 resultList.add(rightBoundary.get(index));
588             }
589             // close the contour (might not be needed)
590             resultList.add(resultList.get(0));
591             result = resultList.toArray(new OTSPoint3D[] {});
592         }
593         return OTSShape.createAndCleanOTSShape(result);
594     }
595 
596     /** {@inheritDoc} */
597     @Override
598     @SuppressWarnings("checkstyle:designforextension")
599     public DirectedPoint getLocation()
600     {
601         DirectedPoint centroid = this.contour.getLocation();
602         return new DirectedPoint(centroid.x, centroid.y, getZ());
603     }
604 
605     /** {@inheritDoc} */
606     @Override
607     @SuppressWarnings("checkstyle:designforextension")
608     public Bounds getBounds()
609     {
610         return this.contour.getBounds();
611     }
612 
613     /** {@inheritDoc} */
614     @Override
615     @SuppressWarnings("checkstyle:designforextension")
616     public String toString()
617     {
618         return String.format("CSE offset %.2fm..%.2fm, width %.2fm..%.2fm", getDesignLineOffsetAtBegin().getSI(),
619                 getDesignLineOffsetAtEnd().getSI(), getBeginWidth().getSI(), getEndWidth().getSI());
620     }
621 
622     /** {@inheritDoc} */
623     @SuppressWarnings("checkstyle:designforextension")
624     @Override
625     public int hashCode()
626     {
627         final int prime = 31;
628         int result = 1;
629         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
630         result = prime * result + ((this.parentLink == null) ? 0 : this.parentLink.hashCode());
631         return result;
632     }
633 
634     /** {@inheritDoc} */
635     @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
636     @Override
637     public boolean equals(final Object obj)
638     {
639         if (this == obj)
640             return true;
641         if (obj == null)
642             return false;
643         if (getClass() != obj.getClass())
644             return false;
645         CrossSectionElement other = (CrossSectionElement) obj;
646         if (this.id == null)
647         {
648             if (other.id != null)
649                 return false;
650         }
651         else if (!this.id.equals(other.id))
652             return false;
653         if (this.parentLink == null)
654         {
655             if (other.parentLink != null)
656                 return false;
657         }
658         else if (!this.parentLink.equals(other.parentLink))
659             return false;
660         return true;
661     }
662 
663     /**
664      * Clone the CrossSectionElement for e.g., copying a network.
665      * @param newParentLink CrossSectionLink; the new link to which the clone belongs
666      * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator for this network
667      * @return a clone of this object
668      * @throws NetworkException in case the cloning fails
669      */
670     @SuppressWarnings("checkstyle:designforextension")
671     public abstract CrossSectionElement clone(CrossSectionLink newParentLink, SimulatorInterface.TimeDoubleUnit newSimulator)
672             throws NetworkException;
673 }