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