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