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: 3281 $, $LastChangedDate: 2017-01-16 01:48:07 +0100 (Mon, 16 Jan 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     /**
102      * @param id String; the id of the GTU
103      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
104      * @param simulator OTSDEVSSimulatorInterface; the simulator to schedule plan changes on
105      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
106      * @throws GTUException when the preconditions of the constructor are not met
107      */
108     @SuppressWarnings("checkstyle:parameternumber")
109     public AbstractGTU(final String id, final GTUType gtuType, final OTSDEVSSimulatorInterface simulator,
110             final PerceivableContext perceivableContext) throws GTUException
111     {
112         Throw.when(id == null, GTUException.class, "id is null");
113         Throw.when(gtuType == null, GTUException.class, "gtuType is null");
114         Throw.when(gtuType.equals(GTUType.NONE), GTUException.class, "gtuType of an actual GTU cannot be GTUType.NONE");
115         Throw.when(gtuType.equals(GTUType.ALL), GTUException.class, "gtuType of an actual GTU cannot be GTUType.ALL");
116         Throw.when(perceivableContext == null, GTUException.class, "perceivableContext is null for GTU with id %s", id);
117         Throw.when(perceivableContext.containsGtuId(id), GTUException.class,
118                 "GTU with id %s already registered in perceivableContext %s", id, perceivableContext.getId());
119         Throw.when(simulator == null, GTUException.class, "simulator is null for GTU with id %s", id);
120 
121         this.id = id;
122         this.uniqueNumber = ++staticUNIQUENUMBER;
123         this.gtuType = gtuType;
124         this.simulator = simulator;
125         this.odometer = Length.ZERO;
126         this.perceivableContext = perceivableContext;
127         this.perceivableContext.addGTU(this);
128     }
129 
130     /**
131      * @param idGenerator IdGenerator; the generator that will produce a unique id of the GTU
132      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
133      * @param simulator OTSDEVSSimulatorInterface; the simulator to schedule plan changes on
134      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
135      * @throws GTUException when the preconditions of the constructor are not met
136      */
137     @SuppressWarnings("checkstyle:parameternumber")
138     public AbstractGTU(final IdGenerator idGenerator, final GTUType gtuType, final OTSDEVSSimulatorInterface simulator,
139             final PerceivableContext perceivableContext) throws GTUException
140     {
141         this(generateId(idGenerator), gtuType, simulator, perceivableContext);
142     }
143 
144     /**
145      * Initialize the GTU at a location and speed, and give it a mission to fulfill through the strategical planner.
146      * @param strategicalPlanner StrategicalPlanner; the strategical planner responsible for the overall 'mission' of the GTU,
147      *            usually indicating where it needs to go. It operates by instantiating tactical planners to do the work.
148      * @param initialLocation DirectedPoint; the initial location (and direction) of the GTU
149      * @param initialSpeed Speed; the initial speed of the GTU
150      * @throws SimRuntimeException when scheduling after the first move fails
151      * @throws GTUException when the preconditions of the parameters are not met or when the construction of the original
152      *             waiting path fails
153      */
154     @SuppressWarnings({ "checkstyle:hiddenfield", "hiding", "checkstyle:designforextension" })
155     public void init(final StrategicalPlanner strategicalPlanner, final DirectedPoint initialLocation,
156             final Speed initialSpeed) throws SimRuntimeException, GTUException
157     {
158         Throw.when(strategicalPlanner == null, GTUException.class, "strategicalPlanner is null for GTU with id %s", this.id);
159         Throw.whenNull(initialLocation, "Initial location of GTU cannot be null");
160         Throw.when(Double.isNaN(initialLocation.x) || Double.isNaN(initialLocation.y) || Double.isNaN(initialLocation.z),
161                 GTUException.class, "initialLocation %s invalid for GTU with id %s", initialLocation, this.id);
162         Throw.when(initialSpeed == null, GTUException.class, "initialSpeed is null for GTU with id %s", this.id);
163         Throw.when(!getId().equals(strategicalPlanner.getGtu().getId()), GTUException.class,
164                 "GTU %s is initialized with a strategical planner for GTU %s", getId(), strategicalPlanner.getGtu().getId());
165 
166         this.strategicalPlanner = strategicalPlanner;
167         Time now = this.simulator.getSimulatorTime().getTime();
168 
169         // Give the GTU a 1 micrometer long operational plan, or a stand-still plan, so the first move will work
170         DirectedPoint p = initialLocation;
171         try
172         {
173             if (initialSpeed.si < OperationalPlan.DRIFTING_SPEED_SI)
174             {
175                 this.operationalPlan = new OperationalPlan(this, p, now, new Duration(1E-6, TimeUnit.SECOND));
176             }
177             else
178             {
179                 OTSPoint3D p2 = new OTSPoint3D(p.x + 1E-6 * Math.cos(p.getRotZ()), p.y + 1E-6 * Math.sin(p.getRotZ()), p.z);
180                 OTSLine3D path = new OTSLine3D(new OTSPoint3D(p), p2);
181                 this.operationalPlan = OperationalPlanBuilder.buildConstantSpeedPlan(this, path, now, initialSpeed);
182             }
183 
184             fireTimedEvent(GTU.INIT_EVENT, new Object[] { getId(), initialLocation, getLength(), getWidth(), getBaseColor() },
185                     now);
186 
187             // and do the real move
188             move(initialLocation);
189         }
190         catch (OperationalPlanException | OTSGeometryException | NetworkException | ParameterException exception)
191         {
192             throw new GTUException("Failed to create OperationalPlan for GTU " + this.id, exception);
193         }
194     }
195 
196     /**
197      * Generate an id, but check first that we have a valid IdGenerator.
198      * @param idGenerator IdGenerator; the generator that will produce a unique id of the GTU
199      * @return a (hopefully unique) Id of the GTU
200      * @throws GTUException when the idGenerator is null
201      */
202     private static String generateId(final IdGenerator idGenerator) throws GTUException
203     {
204         Throw.when(idGenerator == null, GTUException.class, "AbstractGTU.<init>: idGenerator is null");
205         return idGenerator.nextId();
206     }
207 
208     /**
209      * Destructor. Don't forget to call with super.destroy() from any override to avoid memory leaks in the network.
210      */
211     @Override
212     @SuppressWarnings("checkstyle:designforextension")
213     public void destroy()
214     {
215         fireTimedEvent(GTU.DESTROY_EVENT, new Object[] { getId(), getLocation(), getOdometer() },
216                 this.simulator.getSimulatorTime());
217 
218         // cancel the next move
219         if (this.nextMoveEvent != null)
220         {
221             this.simulator.cancelEvent(this.nextMoveEvent);
222             this.nextMoveEvent = null;
223         }
224 
225         this.perceivableContext.removeGTU(this);
226         this.destroyed = true;
227     }
228 
229     /**
230      * Move from the current location according to an operational plan to a location that will bring us nearer to reaching the
231      * location provided by the strategical planner. <br>
232      * This method can be overridden to carry out specific behavior during the execution of the plan (e.g., scheduling of
233      * triggers, entering or leaving lanes, etc.). Please bear in mind that the call to super.move() is essential, and that one
234      * has to take care to handle the situation that the plan gets interrupted.
235      * @param fromLocation the last known location (initial location, or end location of the previous operational plan)
236      * @throws SimRuntimeException when scheduling of the next move fails
237      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
238      * @throws GTUException when there is a problem with the state of the GTU when planning a path
239      * @throws NetworkException in case of a problem with the network, e.g., a dead end where it is not expected
240      * @throws ParameterException in there is a parameter problem
241      */
242     @SuppressWarnings("checkstyle:designforextension")
243     protected void move(final DirectedPoint fromLocation)
244             throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
245     {
246         Time now = this.simulator.getSimulatorTime().getTime();
247 
248         // Add the odometer distance from the currently running operational plan.
249         // Because a plan can be interrupted, we explicitly calculate the covered distance till 'now'
250         if (this.operationalPlan != null)
251         {
252             this.odometer = this.odometer.plus(this.operationalPlan.getTraveledDistance(now));
253         }
254 
255         // Do we have an operational plan?
256         // TODO discuss when a new tactical planner may be needed
257         if (this.tacticalPlanner == null)
258         {
259             // Tell the strategical planner to provide a tactical planner
260             this.tacticalPlanner = this.strategicalPlanner.generateTacticalPlanner();
261         }
262         this.operationalPlan = this.tacticalPlanner.generateOperationalPlan(now, fromLocation);
263         if (getOperationalPlan().getAcceleration(Duration.ZERO).si < -10
264                 && getOperationalPlan().getSpeed(Duration.ZERO).si > 2.5)
265         {
266             System.err.println("(getOperationalPlan().getAcceleration(Duration.ZERO).si < -10)");
267             // this.tacticalPlanner.generateOperationalPlan(now, fromLocation);
268         }
269 
270         // schedule the next move at the end of the current operational plan
271         // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
272         this.nextMoveEvent = new SimEvent<>(new OTSSimTimeDouble(now.plus(this.operationalPlan.getTotalDuration())), this, this,
273                 "move", new Object[] { this.operationalPlan.getEndLocation() });
274         this.simulator.scheduleEvent(this.nextMoveEvent);
275 
276         fireTimedEvent(GTU.MOVE_EVENT,
277                 new Object[] { getId(), fromLocation, getSpeed(), getAcceleration(), getTurnIndicatorStatus(), getOdometer() },
278                 this.simulator.getSimulatorTime());
279     }
280 
281     /**
282      * Interrupt the move and ask for a new plan. This method can be overridden to carry out the bookkeeping needed when the
283      * current plan gets interrupted.
284      * @throws OperationalPlanException when there was a problem retrieving the location from the running plan
285      * @throws SimRuntimeException when scheduling of the next move fails
286      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
287      * @throws GTUException when there is a problem with the state of the GTU when planning a path
288      * @throws NetworkException in case of a problem with the network, e.g., unreachability of a certain point
289      * @throws ParameterException when there is a problem with a parameter
290      */
291     @SuppressWarnings("checkstyle:designforextension")
292     protected void interruptMove()
293             throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
294     {
295         this.simulator.cancelEvent(this.nextMoveEvent);
296         move(this.operationalPlan.getLocation(this.simulator.getSimulatorTime().getTime()));
297     }
298 
299     /** {@inheritDoc} */
300     @Override
301     public final String getId()
302     {
303         return this.id;
304     }
305 
306     /** {@inheritDoc} */
307     @SuppressWarnings("checkstyle:designforextension")
308     @Override
309     public GTUType getGTUType()
310     {
311         return this.gtuType;
312     }
313 
314     /** {@inheritDoc} */
315     @Override
316     public final RelativePosition getReference()
317     {
318         return RelativePosition.REFERENCE_POSITION;
319     }
320 
321     /**
322      * @return simulator the simulator to schedule plan changes on
323      */
324     @Override
325     public final OTSDEVSSimulatorInterface getSimulator()
326     {
327         return this.simulator;
328     }
329 
330     /**
331      * @return strategicalPlanner the planner responsible for the overall 'mission' of the GTU, usually indicating where it
332      *         needs to go. It operates by instantiating tactical planners to do the work.
333      */
334     @Override
335     @SuppressWarnings("checkstyle:designforextension")
336     public StrategicalPlanner getStrategicalPlanner()
337     {
338         return this.strategicalPlanner;
339     }
340 
341     /**
342      * @return tacticalPlanner the tactical planner that can generate an operational plan
343      */
344     @Override
345     @SuppressWarnings("checkstyle:designforextension")
346     public TacticalPlanner getTacticalPlanner()
347     {
348         // TODO discuss when a new tactical planner may be needed
349         if (null == this.tacticalPlanner)
350         {
351             this.tacticalPlanner = this.strategicalPlanner.generateTacticalPlanner();
352         }
353         return this.tacticalPlanner;
354     }
355 
356     /**
357      * @return operationalPlan the current operational plan, which provides a short-term movement over time
358      */
359     @Override
360     public final OperationalPlan getOperationalPlan()
361     {
362         return this.operationalPlan;
363     }
364 
365     /** {@inheritDoc} */
366     @Override
367     public final Length getOdometer()
368     {
369         if (this.operationalPlan == null)
370         {
371             return this.odometer;
372         }
373         try
374         {
375             return this.odometer.plus(this.operationalPlan.getTraveledDistance(this.simulator.getSimulatorTime().getTime()));
376         }
377         catch (OperationalPlanException ope)
378         {
379             return this.odometer;
380         }
381     }
382 
383     /** {@inheritDoc} */
384     @Override
385     public final Speed getSpeed()
386     {
387         if (this.operationalPlan == null)
388         {
389             return Speed.ZERO;
390         }
391         try
392         {
393             return this.operationalPlan.getSpeed(this.simulator.getSimulatorTime().getTime());
394         }
395         catch (OperationalPlanException ope)
396         {
397             // this should not happen at all...
398             throw new RuntimeException("getSpeed() could not derive a valid speed for the current operationalPlan", ope);
399         }
400     }
401 
402     /** {@inheritDoc} */
403     @Override
404     public final Acceleration getAcceleration()
405     {
406         if (this.operationalPlan == null)
407         {
408             return Acceleration.ZERO;
409         }
410         try
411         {
412             return this.operationalPlan.getAcceleration(this.simulator.getSimulatorTime().getTime());
413         }
414         catch (OperationalPlanException ope)
415         {
416             // this should not happen at all...
417             throw new RuntimeException(
418                     "getAcceleration() could not derive a valid acceleration for the current operationalPlan", ope);
419         }
420     }
421 
422     /**
423      * @return maximumAcceleration
424      */
425     @Override
426     public final Acceleration getMaximumAcceleration()
427     {
428         return this.maximumAcceleration;
429     }
430 
431     /**
432      * @param maximumAcceleration set maximumAcceleration
433      */
434     public final void setMaximumAcceleration(final Acceleration maximumAcceleration)
435     {
436         if (maximumAcceleration.le(Acceleration.ZERO))
437         {
438             throw new RuntimeException("Maximum acceleration of GTU " + this.id + " set to value <= 0");
439         }
440         this.maximumAcceleration = maximumAcceleration;
441     }
442 
443     /**
444      * @return maximumDeceleration
445      */
446     @Override
447     public final Acceleration getMaximumDeceleration()
448     {
449         return this.maximumDeceleration;
450     }
451 
452     /**
453      * @param maximumDeceleration set maximumDeceleration, stored as a negative number
454      */
455     public final void setMaximumDeceleration(final Acceleration maximumDeceleration)
456     {
457         if (maximumDeceleration.ge(Acceleration.ZERO))
458         {
459             throw new RuntimeException("Maximum deceleration of GTU " + this.id + " set to value >= 0");
460         }
461         this.maximumDeceleration = maximumDeceleration;
462     }
463 
464     /** {@inheritDoc} */
465     @Override
466     @SuppressWarnings("checkstyle:designforextension")
467     public DirectedPoint getLocation()
468     {
469         if (this.operationalPlan == null)
470         {
471             System.err.println(
472                     "No operational plan for GTU " + this.id + " at t=" + this.getSimulator().getSimulatorTime().getTime());
473             return new DirectedPoint(0, 0, 0);
474         }
475         try
476         {
477             return this.operationalPlan.getLocation(this.simulator.getSimulatorTime().getTime());
478         }
479         catch (OperationalPlanException exception)
480         {
481             return new DirectedPoint(0, 0, 0);
482         }
483     }
484 
485     /** {@inheritDoc} */
486     @Override
487     public final TurnIndicatorStatus getTurnIndicatorStatus()
488     {
489         return this.turnIndicatorStatus;
490     }
491 
492     /** {@inheritDoc} */
493     @Override
494     public final void setTurnIndicatorStatus(final TurnIndicatorStatus turnIndicatorStatus)
495     {
496         this.turnIndicatorStatus = turnIndicatorStatus;
497     }
498 
499     /** {@inheritDoc} */
500     @Override
501     @SuppressWarnings("checkstyle:designforextension")
502     public Color getBaseColor()
503     {
504         if (this.baseColor == null)
505         {
506             this.baseColor = IDGTUColorer.LEGEND.get(this.uniqueNumber % IDGTUColorer.LEGEND.size()).getColor();
507         }
508         return this.baseColor;
509     }
510 
511     /**
512      * @return whether the GTU is destroyed, for the animation.
513      */
514     public final boolean isDestroyed()
515     {
516         return this.destroyed;
517     }
518 
519     /**
520      * @return perceivableContext
521      */
522     public final PerceivableContext getPerceivableContext()
523     {
524         return this.perceivableContext;
525     }
526 
527     /** {@inheritDoc} */
528     @Override
529     @SuppressWarnings("designforextension")
530     public int hashCode()
531     {
532         final int prime = 31;
533         int result = 1;
534         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
535         result = prime * result + this.uniqueNumber;
536         return result;
537     }
538 
539     /** {@inheritDoc} */
540     @Override
541     @SuppressWarnings({"designforextension", "needbraces"})
542     public boolean equals(final Object obj)
543     {
544         if (this == obj)
545             return true;
546         if (obj == null)
547             return false;
548         if (getClass() != obj.getClass())
549             return false;
550         AbstractGTU other = (AbstractGTU) obj;
551         if (this.id == null)
552         {
553             if (other.id != null)
554                 return false;
555         }
556         else if (!this.id.equals(other.id))
557             return false;
558         if (this.uniqueNumber != other.uniqueNumber)
559             return false;
560         return true;
561     }
562 
563 }