1 package org.opentrafficsim.road.gtu.perception.object;
2
3 import java.util.Optional;
4
5 import org.djunits.value.vdouble.scalar.Acceleration;
6 import org.djunits.value.vdouble.scalar.Duration;
7 import org.djunits.value.vdouble.scalar.Length;
8 import org.djunits.value.vdouble.scalar.Speed;
9 import org.djutils.exceptions.Throw;
10 import org.djutils.exceptions.Try;
11 import org.opentrafficsim.base.parameters.ParameterException;
12 import org.opentrafficsim.base.parameters.ParameterSet;
13 import org.opentrafficsim.base.parameters.Parameters;
14 import org.opentrafficsim.core.gtu.GtuType;
15 import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
16 import org.opentrafficsim.core.network.LateralDirectionality;
17 import org.opentrafficsim.core.network.route.Route;
18 import org.opentrafficsim.road.gtu.LaneBasedGtu;
19 import org.opentrafficsim.road.gtu.perception.GtuTypeAssumptions;
20 import org.opentrafficsim.road.gtu.tactical.TacticalContext;
21 import org.opentrafficsim.road.gtu.tactical.following.CarFollowingModel;
22 import org.opentrafficsim.road.gtu.tactical.util.SpeedLimitUtil;
23 import org.opentrafficsim.road.gtu.tactical.util.lmrs.LmrsParameters;
24 import org.opentrafficsim.road.network.speed.SpeedLimit;
25 import org.opentrafficsim.road.network.speed.SpeedLimits;
26
27 /**
28 * Interface for perceived surrounding GTU's, adding signals, maneuver and behavioral information.
29 * <p>
30 * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
31 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
32 * </p>
33 * @author Alexander Verbraeck
34 * @author Peter Knoppers
35 * @author Wouter Schakel
36 */
37 public interface PerceivedGtu extends PerceivedObject, TacticalContext
38 {
39
40 /**
41 * Returns information on the signals. This includes indicators and braking lights.
42 * @return information on the signals
43 */
44 Signals getSignals();
45
46 /**
47 * Returns information on the maneuver. This includes lane changing and lateral deviation.
48 * @return information on the maneuver
49 */
50 Maneuver getManeuver();
51
52 /**
53 * Returns information on the behavior. This includes the car-following model, parameters, desired speed, route, lane change
54 * desire and social pressure.
55 * @return information on the behavior
56 */
57 Behavior getBehavior();
58
59 // getSpeed() and getAcceleration() implemented to solve duplicate methods in PerceivedObject and TacticalContext
60
61 @Override
62 default Speed getSpeed()
63 {
64 return getKinematics().getSpeed();
65 }
66
67 @Override
68 default Acceleration getAcceleration()
69 {
70 return getKinematics().getAcceleration();
71 }
72
73 // forwarding methods implemented as TacticalContext requires it at this level
74
75 @Override
76 default Parameters getParameters()
77 {
78 return getBehavior().getParameters();
79 }
80
81 @Override
82 default CarFollowingModel getCarFollowingModel()
83 {
84 return getBehavior().getCarFollowingModel();
85 }
86
87 @Override
88 default SpeedLimits getSpeedLimits()
89 {
90 return getBehavior().getSpeedLimits();
91 }
92
93 @Override
94 default Speed getMaximumSpeed()
95 {
96 return getBehavior().getMaximumSpeed();
97 }
98
99 @Override
100 default Speed getDesiredSpeed()
101 {
102 return getBehavior().getDesiredSpeed();
103 }
104
105 @Override
106 default Optional<Route> getRoute()
107 {
108 return getBehavior().getRoute();
109 }
110
111 @Override
112 default LateralDirectionality getLaneChangeDirection()
113 {
114 return getManeuver().getLaneChangeDirection();
115 }
116
117 /**
118 * Signal information.
119 */
120 interface Signals
121 {
122 /** Instance with no signals. */
123 Signals NONE = new Record(TurnIndicatorStatus.NONE, false);
124
125 /**
126 * Returns indicator status.
127 * @param lat direction of indicator.
128 * @return indicator status
129 * @throws IllegalArgumentException when the direction is not LEFT or RIGHT
130 */
131 default boolean isIndicatorOn(final LateralDirectionality lat)
132 {
133 Throw.when(lat == null || lat.equals(LateralDirectionality.NONE), IllegalArgumentException.class,
134 "Lateral direction should be LEFT or RIGHT.");
135 return lat.isLeft() ? getTurnIndicatorStatus().isLeft() : getTurnIndicatorStatus().isRight();
136 }
137
138 /**
139 * Returns the indicator status.
140 * @return the indicator status
141 */
142 TurnIndicatorStatus getTurnIndicatorStatus();
143
144 /**
145 * Returns whether the braking lights are on.
146 * @return whether the braking lights are on
147 */
148 boolean isBrakingLightsOn();
149
150 /**
151 * Wraps a GTU and returns its signals.
152 * @param gtu GTU
153 * @return signals view of the GTU
154 */
155 static Signals of(final LaneBasedGtu gtu)
156 {
157 return of(gtu, gtu.getSimulator().getSimulatorTime());
158 }
159
160 /**
161 * Wraps a GTU and returns its signals.
162 * @param gtu GTU
163 * @param time simulation time of the signals
164 * @return signals view of the GTU
165 */
166 static Signals of(final LaneBasedGtu gtu, final Duration time)
167 {
168 return new Signals()
169 {
170 @Override
171 public TurnIndicatorStatus getTurnIndicatorStatus()
172 {
173 return gtu.getTurnIndicatorStatus(time);
174 }
175
176 @Override
177 public boolean isBrakingLightsOn()
178 {
179 return gtu.isBrakingLightsOn(time);
180 }
181 };
182 }
183
184 /**
185 * Record storing signals information.
186 * @param getTurnIndicatorStatus the indicator status
187 * @param isBrakingLightsOn whether the braking lights are on
188 */
189 record Record(TurnIndicatorStatus getTurnIndicatorStatus, boolean isBrakingLightsOn) implements Signals
190 {
191
192 /**
193 * Constructor.
194 * @param getTurnIndicatorStatus the indicator status
195 * @param isBrakingLightsOn whether the braking lights are on
196 * @throws NullPointerException when getTurnIndicatorStatus is {@code null}
197 */
198 public Record
199 {
200 Throw.whenNull(getTurnIndicatorStatus, "getTurnIndicatorStatus");
201 }
202
203 };
204 }
205
206 /**
207 * Information on the maneuver.
208 */
209 interface Maneuver
210 {
211 /** Instance with no signals. */
212 Maneuver NONE = new Record(false, false, Length.ZERO);
213
214 /**
215 * Returns whether the GTU is changing either left or right.
216 * @param lat lateral lane change direction
217 * @return whether the GTU is changing either left or right
218 * @throws IllegalArgumentException when the direction is not LEFT or RIGHT
219 */
220 default boolean isChangingLane(final LateralDirectionality lat)
221 {
222 Throw.when(lat == null || lat.equals(LateralDirectionality.NONE), IllegalArgumentException.class,
223 "Lateral direction should be LEFT or RIGHT.");
224 return lat.isLeft() ? isChangingLeft() : isChangingRight();
225 }
226
227 /**
228 * Returns whether the GTU is changing lanes to the left.
229 * @return whether the GTU is changing lanes to the left
230 */
231 boolean isChangingLeft();
232
233 /**
234 * Returns whether the GTU is changing lanes to the right.
235 * @return whether the GTU is changing lanes to the right
236 */
237 boolean isChangingRight();
238
239 /**
240 * Returns the lane change direction.
241 * @return lane change direction
242 */
243 default LateralDirectionality getLaneChangeDirection()
244 {
245 return isChangingLeft() ? LateralDirectionality.LEFT
246 : (isChangingRight() ? LateralDirectionality.RIGHT : LateralDirectionality.NONE);
247 }
248
249 /**
250 * Returns the lateral deviation from the lane center line. Positive values are left, negative values are right.
251 * @return lateral deviation from the lane center line
252 */
253 Length getDeviation();
254
255 /**
256 * Wraps a GTU and returns its signals.
257 * @param gtu GTU
258 * @return signals view of the GTU
259 */
260 static Maneuver of(final LaneBasedGtu gtu)
261 {
262 return of(gtu, gtu.getSimulator().getSimulatorTime());
263 }
264
265 /**
266 * Wraps a GTU and returns its maneuver at given time.
267 * @param gtu GTU
268 * @param time time of the maneuver
269 * @return maneuver view of the GTU
270 */
271 static Maneuver of(final LaneBasedGtu gtu, final Duration time)
272 {
273 Throw.whenNull(gtu, "gtu");
274 Throw.whenNull(time, "time");
275 return new Maneuver()
276 {
277 @Override
278 public boolean isChangingLeft()
279 {
280 return gtu.getLaneChangeDirection(time).isLeft();
281 }
282
283 @Override
284 public boolean isChangingRight()
285 {
286 return gtu.getLaneChangeDirection(time).isRight();
287 }
288
289 @Override
290 public LateralDirectionality getLaneChangeDirection()
291 {
292 return gtu.getLaneChangeDirection(time);
293 }
294
295 @Override
296 public Length getDeviation()
297 {
298 return gtu.getDeviation(time);
299 }
300 };
301 }
302
303 /**
304 * Record storing signals information.
305 * @param isChangingLeft whether the GTU is changing lanes to the left
306 * @param isChangingRight whether the GTU is changing lanes to the right
307 * @param getDeviation lateral deviation from the lane center line
308 */
309 record Record(boolean isChangingLeft, boolean isChangingRight, Length getDeviation) implements Maneuver
310 {
311
312 /**
313 * Constructor.
314 * @param isChangingLeft whether the GTU is changing lanes to the left
315 * @param isChangingRight whether the GTU is changing lanes to the right
316 * @param getDeviation lateral deviation from the lane center line
317 * @throws IllegalArgumentException when both {@code isChangingLeft} and {@code isChangingRight} are true
318 * @throws NullPointerException when {@code getDeviation} is {@code null}
319 */
320 public Record
321 {
322 Throw.when(isChangingLeft && isChangingRight, IllegalArgumentException.class,
323 "Both isChangingLeft and isChangingRight are true.");
324 Throw.whenNull(getDeviation, "getDeviation");
325 }
326 };
327 }
328
329 /**
330 * Information on the behavior.
331 */
332 interface Behavior
333 {
334 /**
335 * Many models that observe a GTU need to predict the imminent behavior of that GTU. Having a car following model of the
336 * observed GTU can help with that. The car following model that is returned can be on a continuum between the actual
337 * car following model of the observed GTU and the own car following model of the observing GTU, not making any
338 * assumptions about the observed GTU. When successive observations of the GTU take place, parameters about its behavior
339 * can be estimated more accurately. Another interesting easy-to-implement solution is to return a car following model
340 * per GTU type, where the following model of a truck can differ from that of a car.
341 * @return a car following model that represents the expected behavior of the observed GTU
342 */
343 CarFollowingModel getCarFollowingModel();
344
345 /**
346 * Many models that observe a GTU need to predict the imminent behavior of that GTU. Having an estimate of the
347 * behavioral characteristics of the observed GTU can help with that. The parameters that are returned can be on a
348 * continuum between the actual parameters of the observed GTU and the own parameters of the observing GTU, not making
349 * any assumptions about the observed GTU. When successive observations of the GTU take place, parameters about its
350 * behavior can be estimated more accurately. Another interesting easy-to-implement solution is to return a set of
351 * parameters per GTU type, where the parameters of a truck can differ from that of a car.
352 * @return the parameters that represent the expected behavior of the observed GTU, in case of exact values a safe copy
353 * is returned
354 */
355 Parameters getParameters();
356
357 /**
358 * Returns a speed limit model that helps in determining the expected behavior of the observed GTU.
359 * @return a speed limit model that helps in determining the expected behavior of the observed GTU
360 */
361 SpeedLimits getSpeedLimits();
362
363 /**
364 * Returns the maximum speed.
365 * @return maximum speed
366 */
367 Speed getMaximumSpeed();
368
369 /**
370 * Returns the perceived desired speed of the neighbor.
371 * @return perceived desired speed of the neighbor
372 */
373 Speed getDesiredSpeed();
374
375 /**
376 * Models responding to other GTU may assume a route of the vehicle, for instance at intersections. The route may be
377 * short, i.e. only over the next intersection. Implementations may return anything from the actual route, a route based
378 * on indicators and other assumptions, or empty if simply not known/estimated.
379 * @return route of GTU, empty if there is no route
380 */
381 Optional<Route> getRoute();
382
383 /**
384 * Returns the perceived left lane change desire, a value between -1 and 1.
385 * @return the perceived left lane change desire, a value between -1 and 1
386 */
387 double leftLaneChangeDesire();
388
389 /**
390 * Returns the perceived right lane change desire, a value between -1 and 1.
391 * @return the perceived right lane change desire, a value between -1 and 1
392 */
393 double rightLaneChangeDesire();
394
395 /**
396 * Returns the perceived social pressure, a value between 0 and 1.
397 * @return the perceived social pressure, a value between 0 and 1
398 */
399 double socialPressure();
400
401 /**
402 * Wraps a GTU and returns its behavior. The given time only applies to the parameters, lane change desire and social
403 * pressure.
404 * @param gtu GTU
405 * @return behavior view of the GTU
406 * @throws NullPointerException when GTU is {@code null}
407 */
408 static Behavior of(final LaneBasedGtu gtu)
409 {
410 Throw.whenNull(gtu, "gtu");
411 return of0(gtu, gtu.getSimulator().getSimulatorTime(), null);
412 }
413
414 /**
415 * Wraps a GTU and returns its behavior. The given time only applies to lane change desire and social pressure.
416 * @param gtu GTU
417 * @param gtuTypeAssumptions assumptions on the GTU type
418 * @return behavior view of the GTU
419 * @throws NullPointerException when any input argument is {@code null}
420 */
421 static Behavior of(final LaneBasedGtu gtu, final GtuTypeAssumptions gtuTypeAssumptions)
422 {
423 Throw.whenNull(gtu, "gtu");
424 Throw.whenNull(gtuTypeAssumptions, "gtuTypeAssumptions");
425 return of0(gtu, gtu.getSimulator().getSimulatorTime(), gtuTypeAssumptions);
426 }
427
428 /**
429 * Wraps a GTU and returns its behavior. The given time only applies to the parameters, lane change desire and social
430 * pressure.
431 * @param gtu GTU
432 * @param time simulation time of the behavior
433 * @return behavior view of the GTU
434 * @throws NullPointerException when any input argument is {@code null}
435 */
436 static Behavior of(final LaneBasedGtu gtu, final Duration time)
437 {
438 return of0(gtu, time, null);
439 }
440
441 /**
442 * Wraps a GTU and returns its behavior. The given time only applies to the lane change desire and social pressure.
443 * @param gtu GTU
444 * @param time simulation time of the behavior
445 * @param gtuTypeAssumptions assumptions on the GTU type
446 * @return behavior view of the GTU
447 * @throws NullPointerException when any input argument is {@code null}
448 */
449 static Behavior of(final LaneBasedGtu gtu, final Duration time, final GtuTypeAssumptions gtuTypeAssumptions)
450 {
451 Throw.whenNull(gtuTypeAssumptions, "gtuTypeAssumptions");
452 return of0(gtu, time, gtuTypeAssumptions);
453 }
454
455 /**
456 * Wraps a GTU and returns its behavior. The given time only applies to the parameters, lane change desire and social
457 * pressure.
458 * @param gtu GTU
459 * @param time simulation time of the behavior
460 * @param gtuTypeAssumptions assumptions on the GTU type, can be {@code null}
461 * @return behavior view of the GTU
462 */
463 private static Behavior of0(final LaneBasedGtu gtu, final Duration time, final GtuTypeAssumptions gtuTypeAssumptions)
464 {
465 Throw.whenNull(gtu, "gtu");
466 Throw.whenNull(time, "time");
467 // parameters are not historical, they could be, but that's really slow
468 Parameters parameters = new ParameterSet(gtu.getParameters());
469 CarFollowingModel carFollowingModel = gtu.getTacticalPlanner().getCarFollowingModel();
470 return new Behavior()
471 {
472 /** Speed limits. */
473 private SpeedLimits speedLimits;
474
475 /** Desired speed. */
476 private Speed desiredSpeed;
477
478 @Override
479 public CarFollowingModel getCarFollowingModel()
480 {
481 return gtuTypeAssumptions == null ? carFollowingModel
482 : gtuTypeAssumptions.getCarFollowingModel(gtu.getType());
483 }
484
485 @Override
486 public Parameters getParameters()
487 {
488 return gtuTypeAssumptions == null ? parameters : gtuTypeAssumptions.getParameters(gtu.getType());
489 }
490
491 @Override
492 public SpeedLimits getSpeedLimits()
493 {
494 if (this.speedLimits == null)
495 {
496 this.speedLimits = gtuTypeAssumptions == null
497 ? Try.assign(() -> gtu.getLane().getSpeedLimits(gtu.getType()),
498 "Unable to obtain speed limit for GTU on lane where it is at.")
499 : new SpeedLimits(new SpeedLimit(
500 gtuTypeAssumptions.getLaneTypeMaxSpeed(gtu.getType(), gtu.getLane().getType()), false),
501 null);
502 }
503 return this.speedLimits;
504 }
505
506 @Override
507 public Speed getMaximumSpeed()
508 {
509 return gtu.getMaximumSpeed();
510 }
511
512 @Override
513 public Speed getDesiredSpeed()
514 {
515 if (this.desiredSpeed == null)
516 {
517 try
518 {
519 this.desiredSpeed =
520 getCarFollowingModel().desiredSpeed(getParameters(), getSpeedLimits(), getMaximumSpeed());
521 }
522 catch (ParameterException ex)
523 {
524 this.desiredSpeed = SpeedLimitUtil.getDesiredSpeedProxy(getSpeedLimits(), getMaximumSpeed());
525 }
526 }
527 return this.desiredSpeed;
528 }
529
530 @Override
531 public Optional<Route> getRoute()
532 {
533 return gtu.getStrategicalPlanner().getRoute();
534 }
535
536 @Override
537 public double leftLaneChangeDesire()
538 {
539 return parameters.getOptionalParameter(LmrsParameters.DLEFT).orElse(0.0);
540 }
541
542 @Override
543 public double rightLaneChangeDesire()
544 {
545 return parameters.getOptionalParameter(LmrsParameters.DRIGHT).orElse(0.0);
546 }
547
548 @Override
549 public double socialPressure()
550 {
551 return parameters.getOptionalParameter(LmrsParameters.SOCIO).orElse(0.0);
552 }
553 };
554 }
555 }
556
557 /**
558 * Returns a view of this perceived headway GTU that returns different values for the headway, speed and acceleration.
559 * @param headway headway
560 * @param speed speed
561 * @param acceleration acceleration
562 * @return copy with different headway, speed and acceleration
563 * @throws NullPointerException when any input argument is {@code null}
564 * @throws IllegalStateException when this perceived GTU is parallel
565 */
566 default PerceivedGtu moved(final Length headway, final Speed speed, final Acceleration acceleration)
567 {
568 Throw.whenNull(headway, "headyway");
569 Throw.whenNull(speed, "speed");
570 Throw.whenNull(acceleration, "acceleration");
571 Throw.when(getKinematics().getOverlap().isParallel(), IllegalStateException.class,
572 "GTU {} is moved in perception, but it is parallel.", getId());
573 return new PerceivedGtu()
574 {
575 @Override
576 public ObjectType getObjectType()
577 {
578 return PerceivedGtu.this.getObjectType();
579 }
580
581 @Override
582 public Length getLength()
583 {
584 return PerceivedGtu.this.getLength();
585 }
586
587 @Override
588 public Kinematics getKinematics()
589 {
590 return new Kinematics()
591 {
592 @Override
593 public Speed getSpeed()
594 {
595 return speed;
596 }
597
598 @Override
599 public Length getDistance()
600 {
601 return headway;
602 }
603
604 @Override
605 public Acceleration getAcceleration()
606 {
607 return acceleration;
608 }
609
610 @Override
611 public boolean isFacingSameDirection()
612 {
613 return PerceivedGtu.this.getKinematics().isFacingSameDirection();
614 }
615
616 @Override
617 public Overlap getOverlap()
618 {
619 return PerceivedGtu.this.getKinematics().getOverlap();
620 }
621 };
622 }
623
624 @Override
625 public String getId()
626 {
627 return PerceivedGtu.this.getId();
628 }
629
630 @Override
631 public GtuType getGtuType()
632 {
633 return PerceivedGtu.this.getGtuType();
634 }
635
636 @Override
637 public Length getWidth()
638 {
639 return PerceivedGtu.this.getWidth();
640 }
641
642 @Override
643 public Signals getSignals()
644 {
645 return PerceivedGtu.this.getSignals();
646 }
647
648 @Override
649 public Maneuver getManeuver()
650 {
651 return PerceivedGtu.this.getManeuver();
652 }
653
654 @Override
655 public Behavior getBehavior()
656 {
657 return PerceivedGtu.this.getBehavior();
658 }
659 };
660 }
661
662 /**
663 * Returns perceived GTU with given kinematics.
664 * @param gtu GTU that is perceived
665 * @param kinematics kinematics for the vehicle
666 * @return perceived view of the GTU
667 * @throws NullPointerException when {@code gtu} is null
668 */
669 static PerceivedGtu of(final LaneBasedGtu gtu, final Kinematics kinematics)
670 {
671 Throw.whenNull(gtu, "gtu");
672 return new PerceivedGtuBase(gtu.getId(), gtu.getType(), gtu.getLength(), gtu.getWidth(), kinematics, Signals.of(gtu),
673 Maneuver.of(gtu), Behavior.of(gtu));
674 }
675
676 /**
677 * Returns perceived GTU at the given time with given kinematics.
678 * @param gtu GTU that is perceived
679 * @param kinematics kinematics for the vehicle
680 * @param time simulation time at which the GTU is perceived
681 * @return perceived view of the GTU
682 * @throws NullPointerException when {@code gtu} is null
683 */
684 static PerceivedGtu of(final LaneBasedGtu gtu, final Kinematics kinematics, final Duration time)
685 {
686 Throw.whenNull(gtu, "gtu");
687 return new PerceivedGtuBase(gtu.getId(), gtu.getType(), gtu.getLength(), gtu.getWidth(), kinematics,
688 Signals.of(gtu, time), Maneuver.of(gtu, time), Behavior.of(gtu, time));
689 }
690
691 }