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