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