1 package org.opentrafficsim.road.gtu.lane.perception.object;
2
3 import java.util.Optional;
4
5 import org.djunits.value.vdouble.scalar.Acceleration;
6 import org.djunits.value.vdouble.scalar.Length;
7 import org.djunits.value.vdouble.scalar.Speed;
8 import org.djutils.base.Identifiable;
9 import org.djutils.exceptions.Throw;
10
11 /**
12 * Interface for perceived objects including kinematics. Kinematics describe either a static or dynamic object at a certain
13 * distance of, or as adjacent to, a reference object (e.g. the perceiving GTU or a conflict).
14 * <p>
15 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
16 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
17 * </p>
18 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
19 * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
20 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
21 */
22 public interface PerceivedObject extends Identifiable, Comparable<PerceivedObject>
23 {
24
25 /**
26 * Returns object type.
27 * @return the (perceived) object Type
28 */
29 ObjectType getObjectType();
30
31 /**
32 * Returns length.
33 * @return the length of the other object
34 */
35 Length getLength();
36
37 /**
38 * Returns information on the kinematics of the perceived object. This includes position, speed, acceleration and overlap.
39 * @return information on the kinematics of the perceived object
40 */
41 Kinematics getKinematics();
42
43 /**
44 * Returns the distance from the kinematics.
45 * @return the distance from the kinematics
46 */
47 default Length getDistance()
48 {
49 return getKinematics().getDistance();
50 }
51
52 /**
53 * Returns the speed from the kinematics.
54 * @return the speed from the kinematics
55 */
56 default Speed getSpeed()
57 {
58 return getKinematics().getSpeed();
59 }
60
61 /**
62 * Returns the acceleration from the kinematics.
63 * @return the acceleration from the kinematics
64 */
65 default Acceleration getAcceleration()
66 {
67 return getKinematics().getAcceleration();
68 }
69
70 /**
71 * Type of object.
72 */
73 enum ObjectType
74 {
75 /** The observed object for headway is a GTU. */
76 GTU,
77
78 /** The observed object for headway is a traffic light. */
79 TRAFFICLIGHT,
80
81 /** The observed object for headway is a generic object. */
82 OBJECT,
83
84 /** There is no observed object, just a distance. */
85 DISTANCEONLY,
86
87 /** Intersection conflict. */
88 CONFLICT,
89
90 /** Stop line. */
91 STOPLINE,
92
93 /** Bus stop. */
94 BUSSTOP;
95
96 /**
97 * Returns whether this object is a GTU or not.
98 * @return whether this object is a GTU or not.
99 */
100 public boolean isGtu()
101 {
102 return this.equals(GTU);
103 }
104
105 /**
106 * Returns whether this object is a GTU or not.
107 * @return whether this object is a GTU or not.
108 */
109 public boolean isTrafficLight()
110 {
111 return this.equals(TRAFFICLIGHT);
112 }
113
114 /**
115 * Returns whether this object is an object or not.
116 * @return whether this object is an object or not.
117 */
118 public boolean isObject()
119 {
120 return this.equals(OBJECT);
121 }
122
123 /**
124 * Returns whether no object was observed and only a distance was stored.
125 * @return whether no object was observed and only a distance was stored.
126 */
127 public boolean isDistanceOnly()
128 {
129 return this.equals(DISTANCEONLY);
130 }
131
132 /**
133 * Returns whether this object is a conflict or not.
134 * @return whether this object is a conflict or not.
135 */
136 public boolean isConflict()
137 {
138 return this.equals(CONFLICT);
139 }
140
141 /**
142 * Returns whether this object is a stop line or not.
143 * @return whether this object is a stop line or not.
144 */
145 public boolean isStopLine()
146 {
147 return this.equals(STOPLINE);
148 }
149
150 /**
151 * Returns whether this object is a bus stop or not.
152 * @return whether this object is a bus stop or not.
153 */
154 public boolean isBusStop()
155 {
156 return this.equals(BUSSTOP);
157 }
158 }
159
160 /**
161 * Information on the kinematics of the perceived object.
162 */
163 interface Kinematics
164 {
165 /**
166 * Retrieve the strongly typed distance to the other object.
167 * @return the distance to the object
168 */
169 Length getDistance();
170
171 /**
172 * Returns speed.
173 * @return the (perceived) speed of the other object
174 */
175 Speed getSpeed();
176
177 /**
178 * Returns acceleration.
179 * @return the (perceived) acceleration of the other object
180 */
181 Acceleration getAcceleration();
182
183 /**
184 * Returns whether the object is facing the same direction.
185 * @return whether the object is facing the same direction
186 */
187 boolean isFacingSameDirection();
188
189 /**
190 * Returns information on the overlap for parallel objects. For objects fully ahead or behind that fact is provided,
191 * with {@code null} overlap values.
192 * @return information on the overlap for parallel objects
193 */
194 Overlap getOverlap();
195
196 /**
197 * Return kinematics for a static object at given distance ahead. Overlap is considered non-existent. The object is
198 * considered to face the same direction, which might not mean much for a static object.
199 * @param distance distance to object
200 * @return kinematics for a static object at given distance
201 */
202 static Kinematics staticAhead(final Length distance)
203 {
204 return new Record(distance, Speed.ZERO, Acceleration.ZERO, true, Overlap.AHEAD);
205 }
206
207 /**
208 * Return kinematics for a static object at given distance behind. Overlap is considered non-existent. The object is
209 * considered to face the same direction, which might not mean much for a static object.
210 * @param distance distance to object
211 * @return kinematics for a static object at given distance
212 */
213 static Kinematics staticBehind(final Length distance)
214 {
215 return new Record(distance, Speed.ZERO, Acceleration.ZERO, true, Overlap.BEHIND);
216 }
217
218 /**
219 * Return kinematics for a dynamic object ahead. The distance may be negative up to an absolute value equal to the
220 * object length plus the ego length.
221 * @param distance distance from ego front to object rear (or front when not facing the same direction)
222 * @param objectSpeed speed of perceived object
223 * @param objectAcceleration acceleration of perceived object
224 * @param facingSameDirection whether the object is facing the same direction
225 * @param objectLength object length
226 * @param referenceLength length of reference object, usually the perceiving GTU
227 * @return kinematic for a dynamic object
228 * @throws IllegalArgumentException when the distance beyond the extent of object length plus reference length
229 */
230 static Kinematics dynamicAhead(final Length distance, final Speed objectSpeed, final Acceleration objectAcceleration,
231 final boolean facingSameDirection, final Length objectLength, final Length referenceLength)
232 {
233 Throw.whenNull(distance, "distance");
234 Throw.whenNull(objectLength, "objectLength");
235 Throw.whenNull(referenceLength, "referenceLength");
236 Throw.when(distance.si < 0.0 && -distance.si > objectLength.si + referenceLength.si, IllegalArgumentException.class,
237 "Distance is negative beyond the combined length of perceived object and ego.");
238 Overlap overlap;
239 if (distance.ge0())
240 {
241 overlap = Overlap.AHEAD;
242 }
243 else
244 {
245 Length overlapRear = distance.plus(referenceLength);
246 Length overlapVal = distance.neg();
247 Length overlapFront = distance.plus(objectLength);
248 if (overlapRear.lt0())
249 {
250 overlapVal = overlapVal.plus(overlapRear);
251 }
252 if (overlapFront.lt0())
253 {
254 overlapVal = overlapVal.plus(overlapFront);
255 }
256 overlap = new Overlap.Record(overlapVal, overlapFront, overlapRear, false, false);
257 }
258 return new Record(distance, objectSpeed, objectAcceleration, facingSameDirection, overlap);
259 }
260
261 /**
262 * Return kinematics for a dynamic object behind. The distance may be negative up to an absolute value equal to the
263 * object length plus the ego length.
264 * @param distance distance from ego front to object rear (or front when not facing the same direction)
265 * @param objectSpeed speed of perceived object
266 * @param objectAcceleration acceleration of perceived object
267 * @param facingSameDirection whether the object is facing the same direction
268 * @param objectLength object length
269 * @param referenceLength length of reference object, usually the perceiving GTU
270 * @return kinematic for a dynamic object
271 * @throws IllegalArgumentException when the distance beyond the extent of object length plus reference length
272 */
273 static Kinematics dynamicBehind(final Length distance, final Speed objectSpeed, final Acceleration objectAcceleration,
274 final boolean facingSameDirection, final Length objectLength, final Length referenceLength)
275 {
276 Throw.whenNull(distance, "distance");
277 Throw.whenNull(objectLength, "objectLength");
278 Throw.whenNull(referenceLength, "referenceLength");
279 Throw.when(distance.si < 0.0 && -distance.si > objectLength.si + referenceLength.si, IllegalArgumentException.class,
280 "Distance is negative beyond the combined length of perceived object and ego.");
281 Overlap overlap;
282 if (distance.ge0())
283 {
284 overlap = Overlap.BEHIND;
285 }
286 else
287 {
288 Length overlapRear = distance.plus(objectLength).neg();
289 Length overlapVal = distance.neg();
290 Length overlapFront = distance.plus(referenceLength).neg();
291 if (overlapRear.gt0())
292 {
293 overlapVal = overlapVal.minus(overlapRear);
294 }
295 if (overlapFront.gt0())
296 {
297 overlapVal = overlapVal.minus(overlapFront);
298 }
299 overlap = new Overlap.Record(overlapVal, overlapFront, overlapRear, false, false);
300 }
301 return new Record(distance, objectSpeed, objectAcceleration, facingSameDirection, overlap);
302 }
303
304 /**
305 * Record storing kinematics information.
306 * @param getDistance distance
307 * @param getSpeed speed
308 * @param getAcceleration acceleration
309 * @param isFacingSameDirection whether the object is facing the same direction
310 * @param getOverlap overlap
311 */
312 record Record(Length getDistance, Speed getSpeed, Acceleration getAcceleration, boolean isFacingSameDirection,
313 Overlap getOverlap) implements Kinematics
314 {
315 /**
316 * Null checks.
317 * @param getDistance distance
318 * @param getSpeed speed
319 * @param getAcceleration acceleration
320 * @param isFacingSameDirection whether the object is facing the same direction
321 * @param getOverlap overlap
322 */
323 public Record
324 {
325 Throw.whenNull(getDistance, "getDistance");
326 Throw.whenNull(getSpeed, "getSpeed");
327 Throw.whenNull(getAcceleration, "getAcceleration");
328 Throw.whenNull(getOverlap, "getOverlap");
329 }
330 }
331
332 /**
333 * Description of overlap information. If the object is fully ahead or behind, overlap values are {@code null}.
334 */
335 interface Overlap
336 {
337 /** Overlap information for objects ahead. */
338 Overlap AHEAD = new Record((Length) null, null, null, true, false);
339
340 /** Overlap information for objects behind. */
341 Overlap BEHIND = new Record((Length) null, null, null, false, true);
342
343 /**
344 * Return the (perceived) overlap with the other object. This value should be null if there is no overlap. In the
345 * figure below for two GTUs, it is distance b, positive for GTU1 and GTU2.
346 *
347 * <pre>
348 * ----------
349 * | GTU 1 | ----->
350 * ----------
351 * ---------------
352 * | GTU 2 | ----->
353 * ---------------
354 * | a | b | c |
355 * </pre>
356 *
357 * @return the (perceived) overlap with the other object or empty if there is no overlap
358 */
359 Optional<Length> getOverlap();
360
361 /**
362 * Return the (perceived) front overlap to the other object. This value should be null if there is no overlap. In
363 * the figure for two GTUs below, it is distance c, positive for GTU1, negative for GTU2.
364 *
365 * <pre>
366 * ----------
367 * | GTU 1 | ----->
368 * ----------
369 * ---------------
370 * | GTU 2 | ----->
371 * ---------------
372 * | a | b | c |
373 * </pre>
374 *
375 * @return the (perceived) front overlap to the other object or empty if there is no overlap
376 */
377 Optional<Length> getOverlapFront();
378
379 /**
380 * Return the (perceived) rear overlap to the other object. This value should be null if there is no overlap.In the
381 * figure below for two GTUs, it is distance a, positive for GTU1, negative for GTU2.
382 *
383 * <pre>
384 * ----------
385 * | GTU 1 | ----->
386 * ----------
387 * ---------------
388 * | GTU 2 | ----->
389 * ---------------
390 * | a | b | c |
391 * </pre>
392 *
393 * @return the (perceived) rear overlap to the other object or empty if there is no overlap
394 */
395 Optional<Length> getOverlapRear();
396
397 /**
398 * Returns whether the object is fully ahead.
399 * @return whether the other object is in front of the reference object
400 */
401 boolean isAhead();
402
403 /**
404 * Returns whether the object is fully behind.
405 * @return whether the other object is behind the reference object
406 */
407 boolean isBehind();
408
409 /**
410 * Returns whether the object is parallel, partially or fully.
411 * @return whether the other object is parallel the reference object
412 */
413 default boolean isParallel()
414 {
415 return getOverlap().isPresent();
416 }
417
418 /**
419 * Record storing overlap information. The three overlap values are either all {@code null} or they all have a
420 * value. In the former case, either of {@code isAhead} and {@code isBehind} is true.
421 * @param getOverlap overlap
422 * @param getOverlapFront front overlap
423 * @param getOverlapRear rear overlap
424 * @param isAhead whether the object is ahead
425 * @param isBehind whether the object is behind
426 */
427 record Record(Optional<Length> getOverlap, Optional<Length> getOverlapFront, Optional<Length> getOverlapRear,
428 boolean isAhead, boolean isBehind) implements Overlap
429 {
430
431 /**
432 * Constructor.
433 * @param getOverlap overlap
434 * @param getOverlapFront front overlap
435 * @param getOverlapRear rear overlap
436 * @param isAhead whether the object is ahead
437 * @param isBehind whether the object is behind
438 * @throws NullPointerException when getOverlapFront or getOverlapRear is null while getOverlap is not
439 * @throws IllegalArgumentException when getOverlapFront or getOverlapRear is not null while getOverlap is null
440 * @throws IllegalArgumentException if isAhead or isBehind is true while overlap is specified
441 */
442 public Record(final Length getOverlap, final Length getOverlapFront, final Length getOverlapRear,
443 final boolean isAhead, final boolean isBehind)
444 {
445 this(Optional.ofNullable(getOverlap), Optional.ofNullable(getOverlapFront),
446 Optional.ofNullable(getOverlapRear), isAhead, isBehind);
447 if (getOverlap == null)
448 {
449 Throw.when(getOverlapFront != null, IllegalArgumentException.class,
450 "getOverlapFront is not null while getOverlap is null.");
451 Throw.when(getOverlapRear != null, IllegalArgumentException.class,
452 "getOverlapRear is not null while getOverlap is null.");
453 Throw.when(isAhead == isBehind, IllegalArgumentException.class,
454 "if getOverlap is null either of isAhead or isBehind, but not both, should be true.");
455 }
456 else
457 {
458 Throw.whenNull(getOverlapFront, "getOverlapFront is null while getOverlap is not null.");
459 Throw.whenNull(getOverlapRear, "getOverlapRear is null while getOverlap is not null.");
460 Throw.when(isAhead, IllegalArgumentException.class,
461 "if getOverlap is not null isAhead should be false.");
462 Throw.when(isBehind, IllegalArgumentException.class,
463 "if getOverlap is not null isBehind should be false.");
464 }
465 }
466 }
467 }
468 }
469
470 @Override
471 default int compareTo(final PerceivedObject headway)
472 {
473 return getKinematics().getDistance().compareTo(headway.getKinematics().getDistance());
474 }
475 }