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<CrossSectionSlice>; 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 }