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