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