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
23
24
25
26
27
28
29
30
31 public abstract class CrossSectionElement extends LocalEventProducer implements OtsLocatable, Serializable, Identifiable
32 {
33
34 private static final long serialVersionUID = 20150826L;
35
36
37 private final String id;
38
39
40 @SuppressWarnings("checkstyle:visibilitymodifier")
41 protected final CrossSectionLink link;
42
43
44 private final OtsLine2d centerLine;
45
46
47 private final Polygon2d contour;
48
49
50 private final ContinuousDoubleFunction offset;
51
52
53 private final ContinuousDoubleFunction width;
54
55
56 private final OrientedPoint2d location;
57
58
59 private final Bounds2d bounds;
60
61
62 private final OtsShape shape;
63
64
65
66
67
68
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
89 link.getNetwork().clearLaneChangeInfoCache();
90 }
91
92
93
94
95
96 public final CrossSectionLink getLink()
97 {
98 return this.link;
99 }
100
101
102
103
104 public final RoadNetwork getNetwork()
105 {
106 return this.link.getNetwork();
107 }
108
109
110
111
112
113
114 public final Length getLateralCenterPosition(final double fractionalPosition)
115 {
116 return Length.instantiateSI(this.offset.apply(fractionalPosition));
117 }
118
119
120
121
122
123
124 public final Length getLateralCenterPosition(final Length longitudinalPosition)
125 {
126 return getLateralCenterPosition(longitudinalPosition.si / getLength().si);
127 }
128
129
130
131
132
133
134 public final Length getWidth(final Length longitudinalPosition)
135 {
136 return getWidth(longitudinalPosition.si / getLength().si);
137 }
138
139
140
141
142
143
144 public final Length getWidth(final double fractionalPosition)
145 {
146 return Length.instantiateSI(this.width.apply(fractionalPosition));
147 }
148
149
150
151
152
153 public final Length getLength()
154 {
155 return this.centerLine.getTypedLength();
156 }
157
158
159
160
161
162 public final Length getOffsetAtBegin()
163 {
164 return Length.instantiateSI(this.offset.apply(0.0));
165 }
166
167
168
169
170
171 public final Length getOffsetAtEnd()
172 {
173 return Length.instantiateSI(this.offset.apply(1.0));
174 }
175
176
177
178
179
180 public final Length getBeginWidth()
181 {
182 return Length.instantiateSI(this.width.apply(0.0));
183 }
184
185
186
187
188
189 public final Length getEndWidth()
190 {
191 return Length.instantiateSI(this.width.apply(1.0));
192 }
193
194
195
196
197
198 @Override
199 public double getZ()
200 {
201
202 return Try.assign(() -> OtsLocatable.super.getZ(), "Remote exception on calling getZ()");
203 }
204
205
206
207
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
234
235
236 public final String getFullId()
237 {
238 return getLink().getId() + "." + this.id;
239 }
240
241
242
243
244
245
246
247
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
268
269
270
271
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
295
296
297
298 public Length getElevation(final Length position)
299 {
300 return getElevation(position.si / getLength().si);
301 }
302
303
304
305
306
307
308 public Length getElevation(final double fractionalPosition)
309 {
310 return getLink().getElevation(fractionalPosition);
311 }
312
313
314
315
316
317
318 public double getGrade(final Length position)
319 {
320 return getGrade(position.si / getLength().si);
321 }
322
323
324
325
326
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 }