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