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.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   * Cross section elements are used to compose a CrossSectionLink.
28   * <p>
29   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
30   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
31   * <p>
32   * $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
33   * initial version Aug 19, 2014 <br>
34   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
35   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
36   * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
37   */
38  public abstract class CrossSectionElement extends EventProducer implements Locatable, Serializable, Identifiable, Drawable
39  {
40      /** */
41      private static final long serialVersionUID = 20150826L;
42  
43      /** The id. Should be unique within the parentLink. */
44      private final String id;
45  
46      /** Cross Section Link to which the element belongs. */
47      @SuppressWarnings("checkstyle:visibilitymodifier")
48      protected final CrossSectionLink parentLink;
49  
50      /** The offsets and widths at positions along the line, relative to the design line of the parent link. */
51      @SuppressWarnings("checkstyle:visibilitymodifier")
52      protected final List<CrossSectionSlice> crossSectionSlices;
53  
54      /** The length of the line. Calculated once at the creation. */
55      @SuppressWarnings("checkstyle:visibilitymodifier")
56      protected final Length length;
57  
58      /** The center line of the element. Calculated once at the creation. */
59      private final OTSLine3D centerLine;
60  
61      /** The contour of the element. Calculated once at the creation. */
62      private final OTSShape contour;
63  
64      /**
65       * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction, with the direction from
66       * the StartNode towards the EndNode as the longitudinal direction.
67       * @param id String; The id of the CrosssSectionElement. Should be unique within the parentLink.
68       * @param parentLink CrossSectionLink; Link to which the element belongs.
69       * @param crossSectionSlices List&lt;CrossSectionSlice&gt;; the offsets and widths at positions along the line, relative to
70       *            the design line of the parent link. If there is just one with and offset, there should just be one element in
71       *            the list with Length = 0. If there are more slices, the last one should be at the length of the design line.
72       *            If not, a NetworkException is thrown.
73       * @throws OTSGeometryException when creation of the geometry fails
74       * @throws NetworkException when id equal to null or not unique, or there are multiple slices and the last slice does not
75       *             end at the length of the design line.
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); // copy of list with immutable slices
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      * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction, with the direction from
128      * the StartNode towards the EndNode as the longitudinal direction.
129      * @param id String; The id of the CrosssSectionElement. Should be unique within the parentLink.
130      * @param parentLink CrossSectionLink; Link to which the element belongs.
131      * @param lateralOffsetAtBegin Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
132      *            design line of the parent Link at the start of the parent Link
133      * @param lateralOffsetAtEnd Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
134      *            design line of the parent Link at the end of the parent Link
135      * @param beginWidth Length; width at start, positioned <i>symmetrically around</i> the design line
136      * @param endWidth Length; width at end, positioned <i>symmetrically around</i> the design line
137      * @throws OTSGeometryException when creation of the geometry fails
138      * @throws NetworkException when id equal to null or not unique
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      * Construct a list of cross section slices. using sinusoidal interpolation for changin lateral offset.
150      * @param parentLink CrossSectionLink; Link to which the element belongs.
151      * @param lateralOffsetAtBegin 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 start of the parent Link
153      * @param lateralOffsetAtEnd Length; the lateral offset of the design line of the new CrossSectionLink with respect to the
154      *            design line of the parent Link at the end of the parent Link
155      * @param beginWidth Length; width at start, positioned <i>symmetrically around</i> the design line
156      * @param endWidth Length; width at end, positioned <i>symmetrically around</i> the design line
157      * @return List&ltCrossSectionSlice&gt;; the cross section slices
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         // Arrays.asList(new CrossSectionSlice[] { new CrossSectionSlice(Length.ZERO, lateralOffsetAtBegin, beginWidth),
182         // new CrossSectionSlice(parentLink.getLength(), lateralOffsetAtEnd, endWidth) })
183         return result;
184     }
185 
186     /**
187      * Clone a CrossSectionElement for a new network.
188      * @param newCrossSectionLink CrossSectionLink; the new link to which the clone belongs
189      * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator for this network
190      * @param cse CrossSectionElement; the element to clone from
191      * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
192      *             or the end node of the link are not registered in the network.
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      * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction, with the direction from
208      * the StartNode towards the EndNode as the longitudinal direction.
209      * @param id String; The id of the CrosssSectionElement. Should be unique within the parentLink.
210      * @param parentLink CrossSectionLink; Link to which the element belongs.
211      * @param lateralOffset Length; the lateral offset of the design line of the new CrossSectionLink with respect to the design
212      *            line of the parent Link
213      * @param width Length; width, positioned <i>symmetrically around</i> the design line
214      * @throws OTSGeometryException when creation of the geometry fails
215      * @throws NetworkException when id equal to null or not unique
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      * @return parentLink.
226      */
227     public final CrossSectionLink getParentLink()
228     {
229         return this.parentLink;
230     }
231 
232     /**
233      * Calculate the slice the fractional position is in.
234      * @param fractionalPosition double; the fractional position between 0 and 1 compared to the design line
235      * @return int; the lower slice number between 0 and number of slices - 1.
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      * Retrieve the lateral offset from the Link design line at the specified longitudinal position.
253      * @param fractionalPosition double; fractional longitudinal position on this Lane
254      * @return Length; the lateralCenterPosition at the specified longitudinal position
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      * Retrieve the lateral offset from the Link design line at the specified longitudinal position.
274      * @param longitudinalPosition Length; the longitudinal position on this Lane
275      * @return Length; the lateralCenterPosition at the specified longitudinal position
276      */
277     public final Length getLateralCenterPosition(final Length longitudinalPosition)
278     {
279         return getLateralCenterPosition(longitudinalPosition.getSI() / getLength().getSI());
280     }
281 
282     /**
283      * Return the width of this CrossSectionElement at a specified longitudinal position.
284      * @param longitudinalPosition Length; the longitudinal position
285      * @return Length; the width of this CrossSectionElement at the specified longitudinal position.
286      */
287     public final Length getWidth(final Length longitudinalPosition)
288     {
289         return getWidth(longitudinalPosition.getSI() / getLength().getSI());
290     }
291 
292     /**
293      * Return the width of this CrossSectionElement at a specified fractional longitudinal position.
294      * @param fractionalPosition double; the fractional longitudinal position
295      * @return Length; the width of this CrossSectionElement at the specified fractional longitudinal position.
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      * Return the length of this CrossSectionElement as measured along the design line (which equals the center line).
315      * @return Length; the length of this CrossSectionElement
316      */
317     public final Length getLength()
318     {
319         return this.length;
320     }
321 
322     /**
323      * Retrieve the offset from the design line at the begin of the parent link.
324      * @return Length; the offset of this CrossSectionElement at the begin of the parent link
325      */
326     public final Length getDesignLineOffsetAtBegin()
327     {
328         return this.crossSectionSlices.get(0).getDesignLineOffset();
329     }
330 
331     /**
332      * Retrieve the offset from the design line at the end of the parent link.
333      * @return Length; the offset of this CrossSectionElement at the end of the parent link
334      */
335     public final Length getDesignLineOffsetAtEnd()
336     {
337         return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getDesignLineOffset();
338     }
339 
340     /**
341      * Retrieve the width at the begin of the parent link.
342      * @return Length; the width of this CrossSectionElement at the begin of the parent link
343      */
344     public final Length getBeginWidth()
345     {
346         return this.crossSectionSlices.get(0).getWidth();
347     }
348 
349     /**
350      * Retrieve the width at the end of the parent link.
351      * @return Length; the width of this CrossSectionElement at the end of the parent link
352      */
353     public final Length getEndWidth()
354     {
355         return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getWidth();
356     }
357 
358     /**
359      * Retrieve the Z offset (used to determine what covers what when drawing).
360      * @return double; the Z-offset for drawing (what's on top, what's underneath).
361      */
362     protected abstract double getZ();
363 
364     /**
365      * Retrieve the center line of this CrossSectionElement.
366      * @return OTSLine3D; the center line of this CrossSectionElement
367      */
368     public final OTSLine3D getCenterLine()
369     {
370         return this.centerLine;
371     }
372 
373     /**
374      * Retrieve the contour of this CrossSectionElement.
375      * @return OTSShape; the contour of this CrossSectionElement
376      */
377     public final OTSShape getContour()
378     {
379         return this.contour;
380     }
381 
382     /**
383      * Retrieve the id of this CrossSectionElement.
384      * @return String; the id of this CrossSectionElement
385      */
386     @Override
387     public final String getId()
388     {
389         return this.id;
390     }
391 
392     /**
393      * Retrieve the id of this CrossSectionElement.
394      * @return String; the id of this CrossSectionElement
395      */
396     public final String getFullId()
397     {
398         return getParentLink().getId() + "." + this.id;
399     }
400 
401     /**
402      * Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
403      * CrossSectionElement at the specified fractional longitudinal position.
404      * @param lateralDirection LateralDirectionality; LEFT, or RIGHT
405      * @param fractionalLongitudinalPosition double; ranges from 0.0 (begin of parentLink) to 1.0 (end of parentLink)
406      * @return Length
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      * Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
445      * CrossSectionElement at the specified longitudinal position.
446      * @param lateralDirection LateralDirectionality; LEFT, or RIGHT
447      * @param longitudinalPosition Length; the position along the length of this CrossSectionElement
448      * @return Length
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      * Construct a buffer geometry by offsetting the linear geometry line with a distance and constructing a so-called "buffer"
458      * around it.
459      * @param cse CrossSectionElement; the cross section element to construct the contour for
460      * @return OTSShape; the geometry belonging to this CrossSectionElement.
461      * @throws OTSGeometryException when construction of the geometry fails
462      * @throws NetworkException when the resulting contour is degenerate (cannot happen; we hope)
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); // close the contour
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             // close the contour (might not be needed)
511             resultList.add(resultList.get(0));
512             result = resultList.toArray(new OTSPoint3D[] {});
513         }
514         return OTSShape.createAndCleanOTSShape(result);
515     }
516 
517     /** {@inheritDoc} */
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     /** {@inheritDoc} */
527     @Override
528     @SuppressWarnings("checkstyle:designforextension")
529     public Bounds getBounds()
530     {
531         return this.contour.getBounds();
532     }
533 
534     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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      * Clone the CrossSectionElement for e.g., copying a network.
586      * @param newParentLink CrossSectionLink; the new link to which the clone belongs
587      * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator for this network
588      * @return a clone of this object
589      * @throws NetworkException in case the cloning fails
590      */
591     @SuppressWarnings("checkstyle:designforextension")
592     public abstract CrossSectionElement clone(CrossSectionLink newParentLink, SimulatorInterface.TimeDoubleUnit newSimulator)
593             throws NetworkException;
594 }