View Javadoc
1   package org.opentrafficsim.core.gtu;
2   
3   import java.awt.Color;
4   
5   import org.djunits.unit.TimeUnit;
6   import org.djunits.value.vdouble.scalar.Acceleration;
7   import org.djunits.value.vdouble.scalar.Duration;
8   import org.djunits.value.vdouble.scalar.Length;
9   import org.djunits.value.vdouble.scalar.Speed;
10  import org.djunits.value.vdouble.scalar.Time;
11  import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
12  import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
13  import org.opentrafficsim.core.geometry.OTSGeometryException;
14  import org.opentrafficsim.core.geometry.OTSLine3D;
15  import org.opentrafficsim.core.geometry.OTSPoint3D;
16  import org.opentrafficsim.core.gtu.animation.IDGTUColorer;
17  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
18  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
19  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanBuilder;
20  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
21  import org.opentrafficsim.core.gtu.plan.strategical.StrategicalPlanner;
22  import org.opentrafficsim.core.gtu.plan.tactical.TacticalPlanner;
23  import org.opentrafficsim.core.idgenerator.IdGenerator;
24  import org.opentrafficsim.core.network.NetworkException;
25  import org.opentrafficsim.core.perception.PerceivableContext;
26  
27  import nl.tudelft.simulation.dsol.SimRuntimeException;
28  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
29  import nl.tudelft.simulation.event.EventProducer;
30  import nl.tudelft.simulation.language.Throw;
31  import nl.tudelft.simulation.language.d3.DirectedPoint;
32  
33  /**
34   * Implements the basic functionalities of any GTU: the ability to move on 3D-space according to a plan.
35   * <p>
36   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
37   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
38   * <p>
39   * @version $Revision: 3448 $, $LastChangedDate: 2017-04-17 17:38:31 +0200 (Mon, 17 Apr 2017) $, by $Author: averbraeck $,
40   *          initial version Oct 22, 2014 <br>
41   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
42   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
43   */
44  public abstract class AbstractGTU extends EventProducer implements GTU
45  {
46      /** */
47      private static final long serialVersionUID = 20140822L;
48  
49      /** The id of the GTU. */
50      private final String id;
51  
52      /** unique number of the GTU. */
53      private final int uniqueNumber;
54  
55      /** the unique number counter. */
56      private static int staticUNIQUENUMBER = 0;
57  
58      /** The type of GTU, e.g. TruckType, CarType, BusType. */
59      private final GTUType gtuType;
60  
61      /** The simulator to schedule activities on. */
62      private final OTSDEVSSimulatorInterface simulator;
63  
64      /** The maximum acceleration. */
65      private Acceleration maximumAcceleration;
66  
67      /** The maximum deceleration, stored as a negative number. */
68      private Acceleration maximumDeceleration;
69  
70      /**
71       * The odometer which measures how much distance have we covered between instantiation and the last completed operational
72       * plan. In order to get a complete odometer reading, the progress of the current plan execution has to be added to this
73       * value.
74       */
75      private Length odometer;
76  
77      /** The strategical planner that can instantiate tactical planners to determine mid-term decisions. */
78      private StrategicalPlanner strategicalPlanner;
79  
80      /** The tactical planner that can generate an operational plan. */
81      private TacticalPlanner tacticalPlanner = null;
82  
83      /** The current operational plan, which provides a short-term movement over time. */
84      private OperationalPlan operationalPlan = null;
85  
86      /** The next move event as scheduled on the simulator, can be used for interrupting the current move. */
87      private SimEvent<OTSSimTimeDouble> nextMoveEvent;
88  
89      /** The model in which this GTU is registered. */
90      private PerceivableContext perceivableContext;
91  
92      /** Turn indicator status. */
93      private TurnIndicatorStatus turnIndicatorStatus = TurnIndicatorStatus.NOTPRESENT;
94  
95      /** Is this GTU destroyed? */
96      private boolean destroyed = false;
97  
98      /** The cached base color. */
99      private Color baseColor = null;
100 
101     /** aligned or not. */
102     // TODO: should be indicated with a Parameter
103     public static boolean ALIGNED = true;
104     
105     /** aligned schedule count. */
106     // TODO: can be removed after testing period
107     public static int ALIGN_COUNT = 0;
108 
109     /**
110      * @param id String; the id of the GTU
111      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
112      * @param simulator OTSDEVSSimulatorInterface; the simulator to schedule plan changes on
113      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
114      * @throws GTUException when the preconditions of the constructor are not met
115      */
116     @SuppressWarnings("checkstyle:parameternumber")
117     public AbstractGTU(final String id, final GTUType gtuType, final OTSDEVSSimulatorInterface simulator,
118             final PerceivableContext perceivableContext) throws GTUException
119     {
120         Throw.when(id == null, GTUException.class, "id is null");
121         Throw.when(gtuType == null, GTUException.class, "gtuType is null");
122         Throw.when(gtuType.equals(GTUType.NONE), GTUException.class, "gtuType of an actual GTU cannot be GTUType.NONE");
123         Throw.when(gtuType.equals(GTUType.ALL), GTUException.class, "gtuType of an actual GTU cannot be GTUType.ALL");
124         Throw.when(perceivableContext == null, GTUException.class, "perceivableContext is null for GTU with id %s", id);
125         Throw.when(perceivableContext.containsGtuId(id), GTUException.class,
126                 "GTU with id %s already registered in perceivableContext %s", id, perceivableContext.getId());
127         Throw.when(simulator == null, GTUException.class, "simulator is null for GTU with id %s", id);
128 
129         this.id = id;
130         this.uniqueNumber = ++staticUNIQUENUMBER;
131         this.gtuType = gtuType;
132         this.simulator = simulator;
133         this.odometer = Length.ZERO;
134         this.perceivableContext = perceivableContext;
135         this.perceivableContext.addGTU(this);
136     }
137 
138     /**
139      * @param idGenerator IdGenerator; the generator that will produce a unique id of the GTU
140      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
141      * @param simulator OTSDEVSSimulatorInterface; the simulator to schedule plan changes on
142      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
143      * @throws GTUException when the preconditions of the constructor are not met
144      */
145     @SuppressWarnings("checkstyle:parameternumber")
146     public AbstractGTU(final IdGenerator idGenerator, final GTUType gtuType, final OTSDEVSSimulatorInterface simulator,
147             final PerceivableContext perceivableContext) throws GTUException
148     {
149         this(generateId(idGenerator), gtuType, simulator, perceivableContext);
150     }
151 
152     /**
153      * Initialize the GTU at a location and speed, and give it a mission to fulfill through the strategical planner.
154      * @param strategicalPlanner StrategicalPlanner; the strategical planner responsible for the overall 'mission' of the GTU,
155      *            usually indicating where it needs to go. It operates by instantiating tactical planners to do the work.
156      * @param initialLocation DirectedPoint; the initial location (and direction) of the GTU
157      * @param initialSpeed Speed; the initial speed of the GTU
158      * @throws SimRuntimeException when scheduling after the first move fails
159      * @throws GTUException when the preconditions of the parameters are not met or when the construction of the original
160      *             waiting path fails
161      */
162     @SuppressWarnings({ "checkstyle:hiddenfield", "hiding", "checkstyle:designforextension" })
163     public void init(final StrategicalPlanner strategicalPlanner, final DirectedPoint initialLocation, final Speed initialSpeed)
164             throws SimRuntimeException, GTUException
165     {
166         Throw.when(strategicalPlanner == null, GTUException.class, "strategicalPlanner is null for GTU with id %s", this.id);
167         Throw.whenNull(initialLocation, "Initial location of GTU cannot be null");
168         Throw.when(Double.isNaN(initialLocation.x) || Double.isNaN(initialLocation.y) || Double.isNaN(initialLocation.z),
169                 GTUException.class, "initialLocation %s invalid for GTU with id %s", initialLocation, this.id);
170         Throw.when(initialSpeed == null, GTUException.class, "initialSpeed is null for GTU with id %s", this.id);
171         Throw.when(!getId().equals(strategicalPlanner.getGtu().getId()), GTUException.class,
172                 "GTU %s is initialized with a strategical planner for GTU %s", getId(), strategicalPlanner.getGtu().getId());
173 
174         this.strategicalPlanner = strategicalPlanner;
175         Time now = this.simulator.getSimulatorTime().getTime();
176 
177         // Give the GTU a 1 micrometer long operational plan, or a stand-still plan, so the first move will work
178         DirectedPoint p = initialLocation;
179         try
180         {
181             if (initialSpeed.si < OperationalPlan.DRIFTING_SPEED_SI)
182             {
183                 this.operationalPlan = new OperationalPlan(this, p, now, new Duration(1E-6, TimeUnit.SECOND));
184             }
185             else
186             {
187                 OTSPoint3D p2 = new OTSPoint3D(p.x + 1E-6 * Math.cos(p.getRotZ()), p.y + 1E-6 * Math.sin(p.getRotZ()), p.z);
188                 OTSLine3D path = new OTSLine3D(new OTSPoint3D(p), p2);
189                 this.operationalPlan = OperationalPlanBuilder.buildConstantSpeedPlan(this, path, now, initialSpeed);
190             }
191 
192             fireTimedEvent(GTU.INIT_EVENT, new Object[] { getId(), initialLocation, getLength(), getWidth(), getBaseColor() },
193                     now);
194 
195             // and do the real move
196             move(initialLocation);
197         }
198         catch (OperationalPlanException | OTSGeometryException | NetworkException | ParameterException exception)
199         {
200             throw new GTUException("Failed to create OperationalPlan for GTU " + this.id, exception);
201         }
202     }
203 
204     /**
205      * Generate an id, but check first that we have a valid IdGenerator.
206      * @param idGenerator IdGenerator; the generator that will produce a unique id of the GTU
207      * @return a (hopefully unique) Id of the GTU
208      * @throws GTUException when the idGenerator is null
209      */
210     private static String generateId(final IdGenerator idGenerator) throws GTUException
211     {
212         Throw.when(idGenerator == null, GTUException.class, "AbstractGTU.<init>: idGenerator is null");
213         return idGenerator.nextId();
214     }
215 
216     /**
217      * Destructor. Don't forget to call with super.destroy() from any override to avoid memory leaks in the network.
218      */
219     @Override
220     @SuppressWarnings("checkstyle:designforextension")
221     public void destroy()
222     {
223         fireTimedEvent(GTU.DESTROY_EVENT, new Object[] { getId(), getLocation(), getOdometer() },
224                 this.simulator.getSimulatorTime());
225 
226         // cancel the next move
227         if (this.nextMoveEvent != null)
228         {
229             this.simulator.cancelEvent(this.nextMoveEvent);
230             this.nextMoveEvent = null;
231         }
232 
233         this.perceivableContext.removeGTU(this);
234         this.destroyed = true;
235     }
236 
237     /**
238      * Move from the current location according to an operational plan to a location that will bring us nearer to reaching the
239      * location provided by the strategical planner. <br>
240      * This method can be overridden to carry out specific behavior during the execution of the plan (e.g., scheduling of
241      * triggers, entering or leaving lanes, etc.). Please bear in mind that the call to super.move() is essential, and that one
242      * has to take care to handle the situation that the plan gets interrupted.
243      * @param fromLocation the last known location (initial location, or end location of the previous operational plan)
244      * @throws SimRuntimeException when scheduling of the next move fails
245      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
246      * @throws GTUException when there is a problem with the state of the GTU when planning a path
247      * @throws NetworkException in case of a problem with the network, e.g., a dead end where it is not expected
248      * @throws ParameterException in there is a parameter problem
249      */
250     @SuppressWarnings("checkstyle:designforextension")
251     protected void move(final DirectedPoint fromLocation)
252             throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
253     {
254         Time now = this.simulator.getSimulatorTime().getTime();
255 
256         // Add the odometer distance from the currently running operational plan.
257         // Because a plan can be interrupted, we explicitly calculate the covered distance till 'now'
258         Length currentOdometer;
259         if (this.operationalPlan != null)
260         {
261             currentOdometer = this.odometer.plus(this.operationalPlan.getTraveledDistance(now));
262         }
263         else
264         {
265             currentOdometer = this.odometer;
266         }
267 
268         // Do we have an operational plan?
269         // TODO discuss when a new tactical planner may be needed
270         if (this.tacticalPlanner == null)
271         {
272             // Tell the strategical planner to provide a tactical planner
273             this.tacticalPlanner = this.strategicalPlanner.generateTacticalPlanner();
274         }
275         this.operationalPlan = this.tacticalPlanner.generateOperationalPlan(now, fromLocation);
276         this.odometer = currentOdometer;
277         if (getOperationalPlan().getAcceleration(Duration.ZERO).si < -10
278                 && getOperationalPlan().getSpeed(Duration.ZERO).si > 2.5)
279         {
280             System.err.println("(getOperationalPlan().getAcceleration(Duration.ZERO).si < -10)");
281             // this.tacticalPlanner.generateOperationalPlan(now, fromLocation);
282         }
283 
284         if (ALIGNED && this.operationalPlan.getTotalDuration().si == 0.5)
285         {
286             // schedule the next move at exactly 0.5 seconds on the clock
287             // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
288             double tNext = Math.floor(2.0 * now.si + 1.0) / 2.0;
289             DirectedPoint p = (tNext - now.si < 0.5) ? this.operationalPlan.getEndLocation()
290                     : this.operationalPlan.getLocation(new Duration(tNext - now.si, TimeUnit.SI));
291             this.nextMoveEvent =
292                     new SimEvent<>(new OTSSimTimeDouble(new Time(tNext, TimeUnit.SI)), this, this, "move", new Object[] { p });
293             ALIGN_COUNT++;
294         }
295         else
296         {
297             // schedule the next move at the end of the current operational plan
298             // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
299             this.nextMoveEvent = new SimEvent<>(new OTSSimTimeDouble(now.plus(this.operationalPlan.getTotalDuration())), this,
300                     this, "move", new Object[] { this.operationalPlan.getEndLocation() });
301         }
302         this.simulator.scheduleEvent(this.nextMoveEvent);
303 
304         fireTimedEvent(GTU.MOVE_EVENT,
305                 new Object[] { getId(), fromLocation, getSpeed(), getAcceleration(), getTurnIndicatorStatus(), getOdometer() },
306                 this.simulator.getSimulatorTime());
307     }
308 
309     /**
310      * Interrupt the move and ask for a new plan. This method can be overridden to carry out the bookkeeping needed when the
311      * current plan gets interrupted.
312      * @throws OperationalPlanException when there was a problem retrieving the location from the running plan
313      * @throws SimRuntimeException when scheduling of the next move fails
314      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
315      * @throws GTUException when there is a problem with the state of the GTU when planning a path
316      * @throws NetworkException in case of a problem with the network, e.g., unreachability of a certain point
317      * @throws ParameterException when there is a problem with a parameter
318      */
319     @SuppressWarnings("checkstyle:designforextension")
320     protected void interruptMove()
321             throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
322     {
323         this.simulator.cancelEvent(this.nextMoveEvent);
324         move(this.operationalPlan.getLocation(this.simulator.getSimulatorTime().getTime()));
325     }
326 
327     /** {@inheritDoc} */
328     @Override
329     public final String getId()
330     {
331         return this.id;
332     }
333 
334     /** {@inheritDoc} */
335     @SuppressWarnings("checkstyle:designforextension")
336     @Override
337     public GTUType getGTUType()
338     {
339         return this.gtuType;
340     }
341 
342     /** {@inheritDoc} */
343     @Override
344     public final RelativePosition getReference()
345     {
346         return RelativePosition.REFERENCE_POSITION;
347     }
348 
349     /**
350      * @return simulator the simulator to schedule plan changes on
351      */
352     @Override
353     public final OTSDEVSSimulatorInterface getSimulator()
354     {
355         return this.simulator;
356     }
357 
358     /**
359      * @return strategicalPlanner the planner responsible for the overall 'mission' of the GTU, usually indicating where it
360      *         needs to go. It operates by instantiating tactical planners to do the work.
361      */
362     @Override
363     @SuppressWarnings("checkstyle:designforextension")
364     public StrategicalPlanner getStrategicalPlanner()
365     {
366         return this.strategicalPlanner;
367     }
368 
369     /**
370      * @return tacticalPlanner the tactical planner that can generate an operational plan
371      */
372     @Override
373     @SuppressWarnings("checkstyle:designforextension")
374     public TacticalPlanner getTacticalPlanner()
375     {
376         // TODO discuss when a new tactical planner may be needed
377         if (null == this.tacticalPlanner)
378         {
379             this.tacticalPlanner = this.strategicalPlanner.generateTacticalPlanner();
380         }
381         return this.tacticalPlanner;
382     }
383 
384     /**
385      * @return operationalPlan the current operational plan, which provides a short-term movement over time
386      */
387     @Override
388     public final OperationalPlan getOperationalPlan()
389     {
390         return this.operationalPlan;
391     }
392 
393     /** {@inheritDoc} */
394     @Override
395     public final Length getOdometer()
396     {
397         if (this.operationalPlan == null)
398         {
399             return this.odometer;
400         }
401         try
402         {
403             return this.odometer.plus(this.operationalPlan.getTraveledDistance(this.simulator.getSimulatorTime().getTime()));
404         }
405         catch (OperationalPlanException ope)
406         {
407             return this.odometer;
408         }
409     }
410 
411     /** {@inheritDoc} */
412     @Override
413     public final Speed getSpeed()
414     {
415         if (this.operationalPlan == null)
416         {
417             return Speed.ZERO;
418         }
419         try
420         {
421             return this.operationalPlan.getSpeed(this.simulator.getSimulatorTime().getTime());
422         }
423         catch (OperationalPlanException ope)
424         {
425             // this should not happen at all...
426             System.err.println("t = " + this.simulator.getSimulatorTime().getTime());
427             System.err.println("op.validity = " + this.operationalPlan.getEndTime());
428             throw new RuntimeException("getSpeed() could not derive a valid speed for the current operationalPlan", ope);
429         }
430     }
431 
432     /** {@inheritDoc} */
433     @Override
434     public final Acceleration getAcceleration()
435     {
436         if (this.operationalPlan == null)
437         {
438             return Acceleration.ZERO;
439         }
440         try
441         {
442             return this.operationalPlan.getAcceleration(this.simulator.getSimulatorTime().getTime());
443         }
444         catch (OperationalPlanException ope)
445         {
446             // this should not happen at all...
447             throw new RuntimeException(
448                     "getAcceleration() could not derive a valid acceleration for the current operationalPlan", ope);
449         }
450     }
451 
452     /**
453      * @return maximumAcceleration
454      */
455     @Override
456     public final Acceleration getMaximumAcceleration()
457     {
458         return this.maximumAcceleration;
459     }
460 
461     /**
462      * @param maximumAcceleration set maximumAcceleration
463      */
464     public final void setMaximumAcceleration(final Acceleration maximumAcceleration)
465     {
466         if (maximumAcceleration.le(Acceleration.ZERO))
467         {
468             throw new RuntimeException("Maximum acceleration of GTU " + this.id + " set to value <= 0");
469         }
470         this.maximumAcceleration = maximumAcceleration;
471     }
472 
473     /**
474      * @return maximumDeceleration
475      */
476     @Override
477     public final Acceleration getMaximumDeceleration()
478     {
479         return this.maximumDeceleration;
480     }
481 
482     /**
483      * @param maximumDeceleration set maximumDeceleration, stored as a negative number
484      */
485     public final void setMaximumDeceleration(final Acceleration maximumDeceleration)
486     {
487         if (maximumDeceleration.ge(Acceleration.ZERO))
488         {
489             throw new RuntimeException("Maximum deceleration of GTU " + this.id + " set to value >= 0");
490         }
491         this.maximumDeceleration = maximumDeceleration;
492     }
493 
494     /** cache time. */
495     private Time cacheLocationTime = new Time(Double.NaN, TimeUnit.SI);
496 
497     /** caced position at time. */
498     private DirectedPoint cacheLocation = null;
499 
500     /** {@inheritDoc} */
501     @Override
502     @SuppressWarnings("checkstyle:designforextension")
503     public DirectedPoint getLocation()
504     {
505         if (this.operationalPlan == null)
506         {
507             System.err.println(
508                     "No operational plan for GTU " + this.id + " at t=" + this.getSimulator().getSimulatorTime().getTime());
509             return new DirectedPoint(0, 0, 0);
510         }
511         try
512         {
513             // cache
514             if (this.cacheLocationTime.si != this.simulator.getSimulatorTime().getTime().si)
515             {
516                 this.cacheLocationTime = this.simulator.getSimulatorTime().getTime();
517                 this.cacheLocation = this.operationalPlan.getLocation(this.cacheLocationTime);
518             }
519             return this.cacheLocation;
520         }
521         catch (OperationalPlanException exception)
522         {
523             return new DirectedPoint(0, 0, 0);
524         }
525     }
526 
527     /** {@inheritDoc} */
528     @Override
529     public final TurnIndicatorStatus getTurnIndicatorStatus()
530     {
531         return this.turnIndicatorStatus;
532     }
533 
534     /** {@inheritDoc} */
535     @Override
536     public final void setTurnIndicatorStatus(final TurnIndicatorStatus turnIndicatorStatus)
537     {
538         this.turnIndicatorStatus = turnIndicatorStatus;
539     }
540 
541     /** {@inheritDoc} */
542     @Override
543     @SuppressWarnings("checkstyle:designforextension")
544     public Color getBaseColor()
545     {
546         if (this.baseColor == null)
547         {
548             this.baseColor = IDGTUColorer.LEGEND.get(this.uniqueNumber % IDGTUColorer.LEGEND.size()).getColor();
549         }
550         return this.baseColor;
551     }
552 
553     /**
554      * @return whether the GTU is destroyed, for the animation.
555      */
556     public final boolean isDestroyed()
557     {
558         return this.destroyed;
559     }
560 
561     /**
562      * @return perceivableContext
563      */
564     public final PerceivableContext getPerceivableContext()
565     {
566         return this.perceivableContext;
567     }
568 
569     /** {@inheritDoc} */
570     @Override
571     @SuppressWarnings("designforextension")
572     public int hashCode()
573     {
574         final int prime = 31;
575         int result = 1;
576         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
577         result = prime * result + this.uniqueNumber;
578         return result;
579     }
580 
581     /** {@inheritDoc} */
582     @Override
583     @SuppressWarnings({ "designforextension", "needbraces" })
584     public boolean equals(final Object obj)
585     {
586         if (this == obj)
587             return true;
588         if (obj == null)
589             return false;
590         if (getClass() != obj.getClass())
591             return false;
592         AbstractGTU other = (AbstractGTU) obj;
593         if (this.id == null)
594         {
595             if (other.id != null)
596                 return false;
597         }
598         else if (!this.id.equals(other.id))
599             return false;
600         if (this.uniqueNumber != other.uniqueNumber)
601             return false;
602         return true;
603     }
604 
605 }