View Javadoc
1   package org.opentrafficsim.road.network.lane;
2   
3   import java.io.Serializable;
4   import java.util.List;
5   
6   import org.djunits.value.vdouble.scalar.Length;
7   import org.djutils.base.Identifiable;
8   import org.djutils.draw.line.Polygon2d;
9   import org.djutils.draw.point.OrientedPoint2d;
10  import org.djutils.event.LocalEventProducer;
11  import org.djutils.exceptions.Throw;
12  import org.djutils.exceptions.Try;
13  import org.opentrafficsim.base.geometry.BoundingPolygon;
14  import org.opentrafficsim.base.geometry.OtsBounds2d;
15  import org.opentrafficsim.base.geometry.OtsLocatable;
16  import org.opentrafficsim.core.animation.Drawable;
17  import org.opentrafficsim.core.geometry.OtsLine2d;
18  import org.opentrafficsim.core.network.LateralDirectionality;
19  import org.opentrafficsim.core.network.NetworkException;
20  import org.opentrafficsim.road.network.RoadNetwork;
21  
22  /**
23   * Cross section elements are used to compose a CrossSectionLink.
24   * <p>
25   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
26   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
27   * </p>
28   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
29   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
30   * @author <a href="https://www.citg.tudelft.nl">Guus Tamminga</a>
31   */
32  public abstract class CrossSectionElement extends LocalEventProducer
33          implements OtsLocatable, Serializable, Identifiable, Drawable
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 link;
44  
45      /** The offsets and widths at positions along the line, relative to the design line of the parent link. */
46      private final SliceInfo sliceInfo;
47  
48      /** The center line of the element. Calculated once at the creation. */
49      private final OtsLine2d centerLine;
50  
51      /** The contour of the element. Calculated once at the creation. */
52      private final Polygon2d contour;
53  
54      /** Location, center of contour. */
55      private final OrientedPoint2d location;
56  
57      /** Bounding box. */
58      private final OtsBounds2d bounds;
59  
60      /**
61       * Constructor.
62       * @param link CrossSectionLink; link.
63       * @param id String; id.
64       * @param centerLine PolyLine2d; center line.
65       * @param contour Polygon2d; contour shape.
66       * @param crossSectionSlices List&lt;CrossSectionSlice&gt;; cross-section slices.
67       * @throws NetworkException when no cross-section slice is defined.
68       */
69      public CrossSectionElement(final CrossSectionLink link, final String id, final OtsLine2d centerLine,
70              final Polygon2d contour, final List<CrossSectionSlice> crossSectionSlices) throws NetworkException
71      {
72          Throw.whenNull(link, "Link may not be null.");
73          Throw.whenNull(id, "Id may not be null.");
74          Throw.whenNull(centerLine, "Center line may not be null.");
75          Throw.whenNull(contour, "Contour may not be null.");
76          Throw.whenNull(crossSectionSlices, "Cross section slices may not be null.");
77          Throw.when(crossSectionSlices.isEmpty(), NetworkException.class, "Need at least 1 cross section slice.");
78          this.link = link;
79          this.id = id;
80          this.centerLine = centerLine;
81          this.location = centerLine.getLocationFractionExtended(0.5);
82          this.contour = contour;
83          this.bounds = BoundingPolygon.geometryToBounds(this.location, contour);
84  
85          this.sliceInfo = new SliceInfo(crossSectionSlices, link.getLength());
86  
87          link.addCrossSectionElement(this);
88  
89          // clear lane change info cache for each cross section element created
90          link.getNetwork().clearLaneChangeInfoCache();
91      }
92  
93      /**
94       * Returns the link of this cross-section element.
95       * @return CrossSectionLink; link of this cross-section element.
96       */
97      public final CrossSectionLink getLink()
98      {
99          return this.link;
100     }
101 
102     /**
103      * @return the road network to which the lane belongs
104      */
105     public final RoadNetwork getNetwork()
106     {
107         return this.link.getNetwork();
108     }
109 
110     /**
111      * Retrieve the lateral offset from the Link design line at the specified longitudinal position.
112      * @param fractionalPosition double; fractional longitudinal position on this Lane
113      * @return Length; the lateralCenterPosition at the specified longitudinal position
114      */
115     public final Length getLateralCenterPosition(final double fractionalPosition)
116     {
117         return this.sliceInfo.getLateralCenterPosition(fractionalPosition);
118     }
119 
120     /**
121      * Retrieve the lateral offset from the Link design line at the specified longitudinal position.
122      * @param longitudinalPosition Length; the longitudinal position on this Lane
123      * @return Length; the lateralCenterPosition at the specified longitudinal position
124      */
125     public final Length getLateralCenterPosition(final Length longitudinalPosition)
126     {
127         return getLateralCenterPosition(longitudinalPosition.getSI() / getLength().getSI());
128     }
129 
130     /**
131      * Return the width of this CrossSectionElement at a specified longitudinal position.
132      * @param longitudinalPosition Length; the longitudinal position
133      * @return Length; the width of this CrossSectionElement at the specified longitudinal position.
134      */
135     public final Length getWidth(final Length longitudinalPosition)
136     {
137         return getWidth(longitudinalPosition.getSI() / getLength().getSI());
138     }
139 
140     /**
141      * Return the width of this CrossSectionElement at a specified fractional longitudinal position.
142      * @param fractionalPosition double; the fractional longitudinal position
143      * @return Length; the width of this CrossSectionElement at the specified fractional longitudinal position.
144      */
145     public final Length getWidth(final double fractionalPosition)
146     {
147         return this.sliceInfo.getWidth(fractionalPosition);
148     }
149 
150     /**
151      * Return the length of this CrossSectionElement as measured along the design line (which equals the center line).
152      * @return Length; the length of this CrossSectionElement
153      */
154     public final Length getLength()
155     {
156         return this.centerLine.getLength();
157     }
158 
159     /**
160      * Retrieve the offset from the design line at the begin of the parent link.
161      * @return Length; the offset of this CrossSectionElement at the begin of the parent link
162      */
163     public final Length getOffsetAtBegin()
164     {
165         return this.sliceInfo.getOffsetAtBegin();
166     }
167 
168     /**
169      * Retrieve the offset from the design line at the end of the parent link.
170      * @return Length; the offset of this CrossSectionElement at the end of the parent link
171      */
172     public final Length getOffsetAtEnd()
173     {
174         return this.sliceInfo.getOffsetAtEnd();
175     }
176 
177     /**
178      * Retrieve the width at the begin of the parent link.
179      * @return Length; the width of this CrossSectionElement at the begin of the parent link
180      */
181     public final Length getBeginWidth()
182     {
183         return this.sliceInfo.getBeginWidth();
184     }
185 
186     /**
187      * Retrieve the width at the end of the parent link.
188      * @return Length; the width of this CrossSectionElement at the end of the parent link
189      */
190     public final Length getEndWidth()
191     {
192         return this.sliceInfo.getEndWidth();
193     }
194 
195     /**
196      * Retrieve the Z offset (used to determine what covers what when drawing).
197      * @return double; the Z-offset for drawing (what's on top, what's underneath).
198      */
199     @Override
200     public double getZ()
201     {
202         // default implementation returns 0.0 in case of a null location or a 2D location
203         return Try.assign(() -> OtsLocatable.super.getZ(), "Remote exception on calling getZ()");
204     }
205 
206     /**
207      * Retrieve the center line of this CrossSectionElement.
208      * @return OtsLine2d; the center line of this CrossSectionElement
209      */
210     public final OtsLine2d getCenterLine()
211     {
212         return this.centerLine;
213     }
214 
215     /**
216      * Retrieve the contour of this CrossSectionElement.
217      * @return Polygon2d; the contour of this CrossSectionElement
218      */
219     public final Polygon2d getContour()
220     {
221         return this.contour;
222     }
223 
224     /**
225      * Retrieve the id of this CrossSectionElement.
226      * @return String; the id of this CrossSectionElement
227      */
228     @Override
229     public final String getId()
230     {
231         return this.id;
232     }
233 
234     /**
235      * Retrieve the id of this CrossSectionElement.
236      * @return String; the id of this CrossSectionElement
237      */
238     public final String getFullId()
239     {
240         return getLink().getId() + "." + this.id;
241     }
242 
243     /**
244      * Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
245      * CrossSectionElement at the specified fractional longitudinal position.
246      * @param lateralDirection LateralDirectionality; LEFT, or RIGHT
247      * @param fractionalLongitudinalPosition double; ranges from 0.0 (begin of parentLink) to 1.0 (end of parentLink)
248      * @return Length
249      */
250     public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
251             final double fractionalLongitudinalPosition)
252     {
253         return this.sliceInfo.getLateralBoundaryPosition(lateralDirection, fractionalLongitudinalPosition);
254     }
255 
256     /**
257      * Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
258      * CrossSectionElement at the specified longitudinal position.
259      * @param lateralDirection LateralDirectionality; LEFT, or RIGHT
260      * @param longitudinalPosition Length; the position along the length of this CrossSectionElement
261      * @return Length
262      */
263     public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
264             final Length longitudinalPosition)
265     {
266         return getLateralBoundaryPosition(lateralDirection, longitudinalPosition.getSI() / getLength().getSI());
267     }
268 
269     /** {@inheritDoc} */
270     @Override
271     @SuppressWarnings("checkstyle:designforextension")
272     public OrientedPoint2d getLocation()
273     {
274         return this.location;
275     }
276 
277     /** {@inheritDoc} */
278     @Override
279     @SuppressWarnings("checkstyle:designforextension")
280     public OtsBounds2d getBounds()
281     {
282         return this.bounds;
283     }
284 
285     /**
286      * Returns the elevation at the given position.
287      * @param position Length; position.
288      * @return Length; elevation at the given position.
289      */
290     public Length getElevation(final Length position)
291     {
292         return getElevation(position.si / getLength().si);
293     }
294 
295     /**
296      * Returns the elevation at the given fractional position.
297      * @param fractionalPosition double; fractional position.
298      * @return Length; elevation at the given fractional position.
299      */
300     public Length getElevation(final double fractionalPosition)
301     {
302         return getLink().getElevation(fractionalPosition);
303     }
304 
305     /**
306      * Returns the grade at the given position, given as delta_h / delta_f, where f is fractional position.
307      * @param position Length; position.
308      * @return double; grade at the given position.
309      */
310     public double getGrade(final Length position)
311     {
312         return getGrade(position.si / getLength().si);
313     }
314 
315     /**
316      * Returns the grade at the given fractional position, given as delta_h / delta_f, where f is fractional position.
317      * @param fractionalPosition double; fractional position.
318      * @return double; grade at the given fractional position.
319      */
320     public double getGrade(final double fractionalPosition)
321     {
322         return getLink().getGrade(fractionalPosition);
323     }
324 
325     /** {@inheritDoc} */
326     @Override
327     @SuppressWarnings("checkstyle:designforextension")
328     public String toString()
329     {
330         return String.format("CSE offset %.2fm..%.2fm, width %.2fm..%.2fm", getOffsetAtBegin().getSI(),
331                 getOffsetAtEnd().getSI(), getBeginWidth().getSI(), getEndWidth().getSI());
332     }
333 
334     /** {@inheritDoc} */
335     @Override
336     @SuppressWarnings("checkstyle:designforextension")
337     public int hashCode()
338     {
339         final int prime = 31;
340         int result = 1;
341         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
342         result = prime * result + ((this.link == null) ? 0 : this.link.hashCode());
343         return result;
344     }
345 
346     /** {@inheritDoc} */
347     @Override
348     @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
349     public boolean equals(final Object obj)
350     {
351         if (this == obj)
352             return true;
353         if (obj == null)
354             return false;
355         if (getClass() != obj.getClass())
356             return false;
357         CrossSectionElement other = (CrossSectionElement) obj;
358         if (this.id == null)
359         {
360             if (other.id != null)
361                 return false;
362         }
363         else if (!this.id.equals(other.id))
364             return false;
365         if (this.link == null)
366         {
367             if (other.link != null)
368                 return false;
369         }
370         else if (!this.link.equals(other.link))
371             return false;
372         return true;
373     }
374 }