View Javadoc
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 }