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