1   package org.opentrafficsim.core.gtu;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.LinkedHashMap;
6   import java.util.LinkedHashSet;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.Set;
10  
11  import org.djunits.unit.DirectionUnit;
12  import org.djunits.unit.DurationUnit;
13  import org.djunits.unit.PositionUnit;
14  import org.djunits.unit.TimeUnit;
15  import org.djunits.value.vdouble.scalar.Acceleration;
16  import org.djunits.value.vdouble.scalar.Direction;
17  import org.djunits.value.vdouble.scalar.Duration;
18  import org.djunits.value.vdouble.scalar.Length;
19  import org.djunits.value.vdouble.scalar.Speed;
20  import org.djunits.value.vdouble.scalar.Time;
21  import org.djunits.value.vdouble.vector.PositionVector;
22  import org.djutils.base.Identifiable;
23  import org.djutils.draw.bounds.Bounds2d;
24  import org.djutils.draw.line.Polygon2d;
25  import org.djutils.draw.point.OrientedPoint2d;
26  import org.djutils.draw.point.Point2d;
27  import org.djutils.event.EventType;
28  import org.djutils.event.LocalEventProducer;
29  import org.djutils.exceptions.Throw;
30  import org.djutils.exceptions.Try;
31  import org.djutils.immutablecollections.Immutable;
32  import org.djutils.immutablecollections.ImmutableLinkedHashMap;
33  import org.djutils.immutablecollections.ImmutableMap;
34  import org.djutils.metadata.MetaData;
35  import org.djutils.metadata.ObjectDescriptor;
36  import org.opentrafficsim.base.HierarchicallyTyped;
37  import org.opentrafficsim.base.geometry.DynamicSpatialObject;
38  import org.opentrafficsim.base.geometry.OffsetRectangleShape;
39  import org.opentrafficsim.base.geometry.OtsLine2d;
40  import org.opentrafficsim.base.geometry.OtsLocatable;
41  import org.opentrafficsim.base.geometry.OtsShape;
42  import org.opentrafficsim.base.parameters.ParameterException;
43  import org.opentrafficsim.base.parameters.Parameters;
44  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
45  import org.opentrafficsim.core.gtu.RelativePosition.Type;
46  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
47  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
48  import org.opentrafficsim.core.gtu.plan.strategical.StrategicalPlanner;
49  import org.opentrafficsim.core.gtu.plan.tactical.TacticalPlanner;
50  import org.opentrafficsim.core.network.NetworkException;
51  import org.opentrafficsim.core.perception.Historical;
52  import org.opentrafficsim.core.perception.HistoricalValue;
53  import org.opentrafficsim.core.perception.HistoryManager;
54  import org.opentrafficsim.core.perception.PerceivableContext;
55  
56  import nl.tudelft.simulation.dsol.SimRuntimeException;
57  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  public class Gtu extends LocalEventProducer
69          implements HierarchicallyTyped<GtuType, Gtu>, DynamicSpatialObject, OtsLocatable, Serializable, Identifiable
70  {
71      
72      private static final long serialVersionUID = 20140822L;
73  
74      
75      private final String id;
76  
77      
78      private final int uniqueNumber;
79  
80      
81      private static int staticUNIQUENUMBER = 0;
82  
83      
84      private final GtuType gtuType;
85  
86      
87      private final OtsSimulatorInterface simulator;
88  
89      
90      private Parameters parameters;
91  
92      
93      private Acceleration maximumAcceleration;
94  
95      
96      private Acceleration maximumDeceleration;
97  
98      
99  
100 
101 
102 
103     private Historical<Length> odometer;
104 
105     
106     private final Historical<StrategicalPlanner> strategicalPlanner;
107 
108     
109     private final Historical<TacticalPlanner<?, ?>> tacticalPlanner;
110 
111     
112     private final Historical<OperationalPlan> operationalPlan;
113 
114     
115     private SimEvent<Duration> nextMoveEvent;
116 
117     
118     private PerceivableContext perceivableContext;
119 
120     
121     private boolean destroyed = false;
122 
123     
124     
125     public static boolean ALIGNED = true;
126 
127     
128     
129     public static int ALIGN_COUNT = 0;
130 
131     
132     private double cachedSpeedTime = Double.NaN;
133 
134     
135     private Speed cachedSpeed = null;
136 
137     
138     private double cachedAccelerationTime = Double.NaN;
139 
140     
141     private Acceleration cachedAcceleration = null;
142 
143     
144     private Gtu parent = null;
145 
146     
147     private Set<Gtu> children = new LinkedHashSet<>();
148 
149     
150     private GtuErrorHandler errorHandler = GtuErrorHandler.THROW;
151 
152     
153     private Polygon2d contour;
154 
155     
156     private final OtsShape shape;
157 
158     
159     private final Map<RelativePosition.Type, RelativePosition> relativePositions = new LinkedHashMap<>();
160 
161     
162     private final RelativePosition frontPos;
163 
164     
165     private final RelativePosition rearPos;
166 
167     
168     private final Set<RelativePosition> contourPoints = new LinkedHashSet<>();
169 
170     
171     private final Length length;
172 
173     
174     private final Length width;
175 
176     
177     private final Speed maximumSpeed;
178 
179     
180     private final Map<String, String> tags = new LinkedHashMap<>();
181 
182     
183     private Bounds2d bounds;
184 
185     
186 
187 
188 
189 
190 
191 
192 
193 
194 
195 
196 
197     @SuppressWarnings("checkstyle:parameternumber")
198     public Gtu(final String id, final GtuType gtuType, final OtsSimulatorInterface simulator,
199             final PerceivableContext perceivableContext, final Length length, final Length width, final Speed maximumSpeed,
200             final Length front, final Length centerOfGravity) throws GtuException
201     {
202         Throw.when(id == null, GtuException.class, "id is null");
203         Throw.when(gtuType == null, GtuException.class, "gtuType is null");
204         Throw.when(perceivableContext == null, GtuException.class, "perceivableContext is null for GTU with id %s", id);
205         Throw.when(perceivableContext.containsGtuId(id), GtuException.class,
206                 "GTU with id %s already registered in perceivableContext %s", id, perceivableContext.getId());
207         Throw.when(simulator == null, GtuException.class, "simulator is null for GTU with id %s", id);
208 
209         this.length = length;
210         this.width = width;
211         if (null == maximumSpeed)
212         {
213             throw new GtuException("maximumSpeed may not be null");
214         }
215         this.maximumSpeed = maximumSpeed;
216 
217         HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
218         this.id = id;
219         this.uniqueNumber = ++staticUNIQUENUMBER;
220         this.gtuType = gtuType;
221         this.simulator = simulator;
222         this.odometer = new HistoricalValue<>(historyManager, this, Length.ZERO);
223         this.perceivableContext = perceivableContext;
224         this.perceivableContext.addGTU(this);
225         this.strategicalPlanner = new HistoricalValue<>(historyManager, this);
226         this.tacticalPlanner = new HistoricalValue<>(historyManager, this, null);
227         this.operationalPlan = new HistoricalValue<>(historyManager, this, null);
228 
229         
230         Length dy2 = width.times(0.5);
231         this.frontPos = new RelativePosition(front, Length.ZERO, Length.ZERO, RelativePosition.FRONT);
232         this.relativePositions.put(RelativePosition.FRONT, this.frontPos);
233         this.rearPos = new RelativePosition(front.minus(length), Length.ZERO, Length.ZERO, RelativePosition.REAR);
234         this.relativePositions.put(RelativePosition.REAR, this.rearPos);
235         this.relativePositions.put(RelativePosition.REFERENCE, RelativePosition.REFERENCE_POSITION);
236         this.relativePositions.put(RelativePosition.CENTER,
237                 new RelativePosition(Length.ZERO, Length.ZERO, Length.ZERO, RelativePosition.CENTER));
238         this.bounds = new Bounds2d(front.minus(length).si, front.si, -width.si / 2.0, width.si / 2.0);
239         this.shape = new OffsetRectangleShape(front.minus(length).si, front.si, -width.si / 2.0, width.si / 2.0);
240 
241         
242         for (int i = -1; i <= 1; i += 2)
243         {
244             Length x = i < 0 ? front.minus(length) : front;
245             for (int j = -1; j <= 1; j += 2)
246             {
247                 this.contourPoints.add(new RelativePosition(x, dy2.times(j), Length.ZERO, RelativePosition.CONTOUR));
248             }
249         }
250     }
251 
252     
253 
254 
255 
256 
257 
258 
259 
260 
261 
262     @SuppressWarnings({"checkstyle:hiddenfield", "hiding", "checkstyle:designforextension"})
263     public void init(final StrategicalPlanner strategicalPlanner, final OrientedPoint2d initialLocation,
264             final Speed initialSpeed) throws SimRuntimeException, GtuException
265     {
266         Throw.when(strategicalPlanner == null, GtuException.class, "strategicalPlanner is null for GTU with id %s", this.id);
267         Throw.whenNull(initialLocation, "Initial location of GTU cannot be null");
268         Throw.when(Double.isNaN(initialLocation.x) || Double.isNaN(initialLocation.y), GtuException.class,
269                 "initialLocation %s invalid for GTU with id %s", initialLocation, this.id);
270         Throw.when(initialSpeed == null, GtuException.class, "initialSpeed is null for GTU with id %s", this.id);
271         Throw.when(!getId().equals(strategicalPlanner.getGtu().getId()), GtuException.class,
272                 "GTU %s is initialized with a strategical planner for GTU %s", getId(), strategicalPlanner.getGtu().getId());
273 
274         this.strategicalPlanner.set(strategicalPlanner);
275         this.tacticalPlanner.set(strategicalPlanner.getTacticalPlanner());
276 
277         try
278         {
279             move(initialLocation);
280         }
281         catch (OperationalPlanException | NetworkException | ParameterException exception)
282         {
283             throw new GtuException("Failed to create OperationalPlan for GTU " + this.id, exception);
284         }
285     }
286 
287     
288     public final RelativePosition getFront()
289     {
290         return this.frontPos;
291     }
292 
293     
294     public final RelativePosition getRear()
295     {
296         return this.rearPos;
297     }
298 
299     
300     public final RelativePosition getCenter()
301     {
302         return this.relativePositions.get(RelativePosition.CENTER);
303     }
304 
305     
306     public final ImmutableMap<Type, RelativePosition> getRelativePositions()
307     {
308         return new ImmutableLinkedHashMap<>(this.relativePositions, Immutable.WRAP);
309     }
310 
311     
312     public final Length getLength()
313     {
314         return this.length;
315     }
316 
317     
318     public final Length getWidth()
319     {
320         return this.width;
321     }
322 
323     
324     public final Speed getMaximumSpeed()
325     {
326         return this.maximumSpeed;
327     }
328 
329     @Override
330     public final Bounds2d getBounds()
331     {
332         return this.bounds;
333     }
334 
335     
336 
337 
338     @SuppressWarnings("checkstyle:designforextension")
339     public void destroy()
340     {
341         OrientedPoint2d location = getLocation();
342         fireTimedEvent(Gtu.DESTROY_EVENT,
343                 new Object[] {getId(), new PositionVector(new double[] {location.x, location.y}, PositionUnit.METER),
344                         new Direction(location.getDirZ(), DirectionUnit.EAST_RADIAN), getOdometer()},
345                 this.simulator.getSimulatorTime());
346 
347         
348         if (this.nextMoveEvent != null)
349         {
350             this.simulator.cancelEvent(this.nextMoveEvent);
351             this.nextMoveEvent = null;
352         }
353 
354         this.perceivableContext.removeGTU(this);
355         this.destroyed = true;
356     }
357 
358     
359 
360 
361 
362 
363 
364 
365 
366 
367 
368 
369 
370 
371     @SuppressWarnings("checkstyle:designforextension")
372     protected boolean move(final OrientedPoint2d fromLocation)
373             throws SimRuntimeException, GtuException, NetworkException, ParameterException
374     {
375         try
376         {
377             Time now = this.simulator.getSimulatorAbsTime();
378 
379             
380             
381             Length currentOdometer;
382             if (this.operationalPlan.get() != null)
383             {
384                 currentOdometer = this.odometer.get().plus(this.operationalPlan.get().getTraveledDistance(now));
385             }
386             else
387             {
388                 currentOdometer = this.odometer.get();
389             }
390 
391             
392             
393             TacticalPlanner<?, ?> tactPlanner = this.tacticalPlanner.get();
394             if (tactPlanner == null)
395             {
396                 
397                 tactPlanner = this.strategicalPlanner.get().getTacticalPlanner();
398                 this.tacticalPlanner.set(tactPlanner);
399             }
400             synchronized (this)
401             {
402                 tactPlanner.getPerception().perceive();
403             }
404             OperationalPlan newOperationalPlan = tactPlanner.generateOperationalPlan(now, fromLocation);
405             synchronized (this)
406             {
407                 this.operationalPlan.set(newOperationalPlan);
408                 this.cachedSpeedTime = Double.NaN;
409                 this.cachedAccelerationTime = Double.NaN;
410                 this.odometer.set(currentOdometer);
411             }
412 
413             
414             if (ALIGNED && newOperationalPlan.getTotalDuration().si == 0.5)
415             {
416                 
417                 
418                 double tNext = Math.floor(2.0 * now.si + 1.0) / 2.0;
419                 OrientedPoint2d p = (tNext - now.si < 0.5) ? newOperationalPlan.getEndLocation()
420                         : newOperationalPlan.getLocation(new Duration(tNext - now.si, DurationUnit.SI));
421                 this.nextMoveEvent =
422                         new SimEvent<Duration>(new Duration(tNext - getSimulator().getStartTimeAbs().si, DurationUnit.SI), this,
423                                 "move", new Object[] {p});
424                 ALIGN_COUNT++;
425             }
426             else
427             {
428                 
429                 
430                 this.nextMoveEvent =
431                         new SimEvent<>(now.plus(newOperationalPlan.getTotalDuration()).minus(getSimulator().getStartTimeAbs()),
432                                 this, "move", new Object[] {newOperationalPlan.getEndLocation()});
433             }
434             this.simulator.scheduleEvent(this.nextMoveEvent);
435             fireTimedEvent(Gtu.MOVE_EVENT,
436                     new Object[] {getId(),
437                             new PositionVector(new double[] {fromLocation.x, fromLocation.y}, PositionUnit.METER),
438                             new Direction(fromLocation.getDirZ(), DirectionUnit.EAST_RADIAN), getSpeed(), getAcceleration(),
439                             getOdometer()},
440                     this.simulator.getSimulatorTime());
441 
442             return false;
443         }
444         catch (Exception ex)
445         {
446             try
447             {
448                 this.errorHandler.handle(this, ex);
449             }
450             catch (Exception exception)
451             {
452                 throw new GtuException(exception);
453             }
454             return true;
455         }
456     }
457 
458     
459 
460 
461 
462 
463 
464 
465 
466     @SuppressWarnings("checkstyle:designforextension")
467     protected void interruptMove() throws SimRuntimeException, GtuException, NetworkException, ParameterException
468     {
469         this.simulator.cancelEvent(this.nextMoveEvent);
470         move(this.operationalPlan.get().getLocation(this.simulator.getSimulatorAbsTime()));
471     }
472 
473     
474     @Override
475     public final String getId()
476     {
477         return this.id;
478     }
479 
480     
481 
482 
483 
484 
485     public void setTag(final String tag, final String value)
486     {
487         this.tags.put(tag, value);
488     }
489 
490     
491 
492 
493 
494 
495     public String getTag(final String tag)
496     {
497         return this.tags.get(tag);
498     }
499 
500     @SuppressWarnings("checkstyle:designforextension")
501     @Override
502     public GtuType getType()
503     {
504         return this.gtuType;
505     }
506 
507     
508     public final RelativePosition getReference()
509     {
510         return RelativePosition.REFERENCE_POSITION;
511     }
512 
513     
514     public final OtsSimulatorInterface getSimulator()
515     {
516         return this.simulator;
517     }
518 
519     
520     public final Parameters getParameters()
521     {
522         return this.parameters;
523     }
524 
525     
526     public final void setParameters(final Parameters parameters)
527     {
528         this.parameters = parameters;
529     }
530 
531     
532 
533 
534 
535     public StrategicalPlanner getStrategicalPlanner()
536     {
537         return this.strategicalPlanner.get();
538     }
539 
540     
541 
542 
543 
544 
545     public StrategicalPlanner getStrategicalPlanner(final Time time)
546     {
547         return this.strategicalPlanner.get(time);
548     }
549 
550     
551     public TacticalPlanner<?, ?> getTacticalPlanner()
552     {
553         return getStrategicalPlanner().getTacticalPlanner();
554     }
555 
556     
557 
558 
559 
560     public TacticalPlanner<?, ?> getTacticalPlanner(final Time time)
561     {
562         return getStrategicalPlanner(time).getTacticalPlanner(time);
563     }
564 
565     
566     public final OperationalPlan getOperationalPlan()
567     {
568         return this.operationalPlan.get();
569     }
570 
571     
572 
573 
574 
575     public final OperationalPlan getOperationalPlan(final Time time)
576     {
577         return this.operationalPlan.get(time);
578     }
579 
580     
581 
582 
583 
584     protected void setOperationalPlan(final OperationalPlan operationalPlan)
585     {
586         this.operationalPlan.set(operationalPlan);
587     }
588 
589     
590 
591 
592     public final Length getOdometer()
593     {
594         return getOdometer(this.simulator.getSimulatorAbsTime());
595     }
596 
597     
598 
599 
600 
601     public final Length getOdometer(final Time time)
602     {
603         synchronized (this)
604         {
605             if (getOperationalPlan(time) == null)
606             {
607                 return this.odometer.get(time);
608             }
609             try
610             {
611                 return this.odometer.get(time).plus(getOperationalPlan(time).getTraveledDistance(time));
612             }
613             catch (OperationalPlanException ope)
614             {
615                 return this.odometer.get(time);
616             }
617         }
618     }
619 
620     
621     public final Speed getSpeed()
622     {
623         synchronized (this)
624         {
625             return getSpeed(this.simulator.getSimulatorAbsTime());
626         }
627     }
628 
629     
630 
631 
632 
633     public final Speed getSpeed(final Time time)
634     {
635         synchronized (this)
636         {
637             if (this.cachedSpeedTime != time.si)
638             {
639                 
640                 this.cachedSpeedTime = Double.NaN;
641                 this.cachedSpeed = null;
642                 OperationalPlan plan = getOperationalPlan(time);
643                 if (plan == null)
644                 {
645                     this.cachedSpeed = Speed.ZERO;
646                 }
647                 else if (time.si < plan.getStartTime().si)
648                 {
649                     this.cachedSpeed = plan.getStartSpeed();
650                 }
651                 else if (time.si > plan.getEndTime().si)
652                 {
653                     if (time.si - plan.getEndTime().si < 1e-6)
654                     {
655                         this.cachedSpeed = Try.assign(() -> plan.getSpeed(plan.getEndTime()),
656                                 "getSpeed() could not derive a valid speed for the current operationalPlan");
657                     }
658                     else
659                     {
660                         throw new IllegalStateException("Requesting speed value beyond plan.");
661                     }
662                 }
663                 else
664                 {
665                     this.cachedSpeed = Try.assign(() -> plan.getSpeed(time),
666                             "getSpeed() could not derive a valid speed for the current operationalPlan");
667                 }
668                 this.cachedSpeedTime = time.si; 
669             }
670             return this.cachedSpeed;
671         }
672     }
673 
674     
675     public final Acceleration getAcceleration()
676     {
677         synchronized (this)
678         {
679             return getAcceleration(this.simulator.getSimulatorAbsTime());
680         }
681     }
682 
683     
684 
685 
686 
687     public final Acceleration getAcceleration(final Time time)
688     {
689         synchronized (this)
690         {
691             if (this.cachedAccelerationTime != time.si)
692             {
693                 
694                 this.cachedAccelerationTime = Double.NaN;
695                 this.cachedAcceleration = null;
696                 OperationalPlan plan = getOperationalPlan(time);
697                 if (plan == null)
698                 {
699                     this.cachedAcceleration = Acceleration.ZERO;
700                 }
701                 else if (time.si < plan.getStartTime().si)
702                 {
703                     this.cachedAcceleration =
704                             Try.assign(() -> plan.getAcceleration(plan.getStartTime()), "Exception obtaining acceleration.");
705                 }
706                 else if (time.si > plan.getEndTime().si)
707                 {
708                     if (time.si - plan.getEndTime().si < 1e-6)
709                     {
710                         this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(plan.getEndTime()),
711                                 "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
712                     }
713                     else
714                     {
715                         throw new IllegalStateException("Requesting acceleration value beyond plan.");
716                     }
717                 }
718                 else
719                 {
720                     this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(time),
721                             "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
722                 }
723                 this.cachedAccelerationTime = time.si;
724             }
725             return this.cachedAcceleration;
726         }
727     }
728 
729     
730 
731 
732     public final Acceleration getMaximumAcceleration()
733     {
734         return this.maximumAcceleration;
735     }
736 
737     
738 
739 
740     public final void setMaximumAcceleration(final Acceleration maximumAcceleration)
741     {
742         if (maximumAcceleration.le(Acceleration.ZERO))
743         {
744             throw new RuntimeException("Maximum acceleration of GTU " + this.id + " set to value <= 0");
745         }
746         this.maximumAcceleration = maximumAcceleration;
747     }
748 
749     
750 
751 
752     public final Acceleration getMaximumDeceleration()
753     {
754         return this.maximumDeceleration;
755     }
756 
757     
758 
759 
760 
761     public final void setMaximumDeceleration(final Acceleration maximumDeceleration)
762     {
763         if (maximumDeceleration.ge(Acceleration.ZERO))
764         {
765             throw new RuntimeException("Cannot set maximum deceleration of GTU " + this.id + " to " + maximumDeceleration
766                     + " (value must be negative)");
767         }
768         this.maximumDeceleration = maximumDeceleration;
769     }
770 
771     
772     private Time cacheLocationTime = new Time(Double.NaN, TimeUnit.DEFAULT);
773 
774     
775     private OrientedPoint2d cacheLocation = null;
776 
777     @Override
778     @SuppressWarnings("checkstyle:designforextension")
779     public OrientedPoint2d getLocation()
780     {
781         synchronized (this)
782         {
783             if (this.operationalPlan.get() == null)
784             {
785                 this.simulator.getLogger().always()
786                         .error("No operational plan for GTU " + this.id + " at t=" + this.getSimulator().getSimulatorTime());
787                 return new OrientedPoint2d(0, 0, 0); 
788             }
789             try
790             {
791                 
792                 Time locationTime = this.simulator.getSimulatorAbsTime();
793                 if (null == this.cacheLocationTime || this.cacheLocationTime.si != locationTime.si)
794                 {
795                     this.cacheLocationTime = null;
796                     this.cacheLocation = this.operationalPlan.get().getLocation(locationTime);
797                     this.cacheLocationTime = locationTime;
798                 }
799                 return this.cacheLocation;
800             }
801             catch (OperationalPlanException exception)
802             {
803                 return new OrientedPoint2d(0, 0, 0);
804             }
805         }
806     }
807 
808     
809 
810 
811 
812 
813 
814 
815     @Override
816     public Polygon2d getContour(final Time time)
817     {
818         try
819         {
820             if (this.contour == null)
821             {
822                 
823                 double w = getWidth().si;
824                 double l = getLength().si;
825                 this.contour = new Polygon2d(new Point2d(-0.5 * l, -0.5 * w), new Point2d(-0.5 * l, 0.5 * w),
826                         new Point2d(0.5 * l, 0.5 * w), new Point2d(0.5 * l, -0.5 * w));
827             }
828             Polygon2d s = OtsLocatable.transformContour(this.contour, this.operationalPlan.get(time).getLocation(time));
829             System.out.println("gtu " + getId() + ", shape(t)=" + s);
830             return s;
831         }
832         catch (OperationalPlanException exception)
833         {
834             throw new RuntimeException(exception);
835         }
836     }
837 
838     
839 
840 
841 
842 
843 
844     @Override
845     public Polygon2d getContour()
846     {
847         try
848         {
849             
850             OtsLine2d path = this.operationalPlan.get().getPath();
851             
852             
853             double rear = Math.max(0.0, getReference().dx().si - getRear().dx().si);
854             double front = path.getLength() + Math.max(0.0, getFront().dx().si - getReference().dx().si);
855             Point2d p0 = path.getLocationExtendedSI(-rear);
856             Point2d pn = path.getLocationExtendedSI(front);
857             List<Point2d> pList = path.getPointList();
858             pList.add(0, p0);
859             pList.add(pn);
860             OtsLine2d extendedPath = new OtsLine2d(pList);
861             List<Point2d> swath = new ArrayList<>();
862             swath.addAll(extendedPath.offsetLine(getWidth().si / 2.0).getPointList());
863             swath.addAll(extendedPath.offsetLine(-getWidth().si / 2.0).reverse().getPointList());
864             Polygon2d s = new Polygon2d(swath);
865             
866             
867             return s;
868         }
869         catch (Exception e)
870         {
871             throw new RuntimeException(e);
872         }
873     }
874 
875     @Override
876     public OtsShape getShape()
877     {
878         return this.shape;
879     }
880 
881     
882 
883 
884 
885     public final boolean isDestroyed()
886     {
887         return this.destroyed;
888     }
889 
890     
891     public PerceivableContext getPerceivableContext()
892     {
893         return this.perceivableContext;
894     }
895 
896     
897 
898 
899 
900 
901     public void addGtu(final Gtu gtu) throws GtuException
902     {
903         this.children.add(gtu);
904         gtu.setParent(this);
905     }
906 
907     
908 
909 
910 
911     public void removeGtu(final Gtu gtu)
912     {
913         this.children.remove(gtu);
914         try
915         {
916             gtu.setParent(null);
917         }
918         catch (GtuException exception)
919         {
920             
921         }
922     }
923 
924     
925 
926 
927 
928 
929     public void setParent(final Gtu gtu) throws GtuException
930     {
931         Throw.when(gtu != null && this.parent != null, GtuException.class, "GTU %s already has a parent.", this);
932         this.parent = gtu;
933     }
934 
935     
936 
937 
938 
939     public Gtu getParent()
940     {
941         return this.parent;
942     }
943 
944     
945 
946 
947 
948     public Set<Gtu> getChildren()
949     {
950         return new LinkedHashSet<>(this.children); 
951     }
952 
953     
954 
955 
956     protected GtuErrorHandler getErrorHandler()
957     {
958         return this.errorHandler;
959     }
960 
961     
962 
963 
964 
965     public void setErrorHandler(final GtuErrorHandler errorHandler)
966     {
967         this.errorHandler = errorHandler;
968     }
969 
970     
971 
972 
973 
974     public final SimEvent<Duration> getNextMoveEvent()
975     {
976         return this.nextMoveEvent;
977     }
978 
979     @Override
980     @SuppressWarnings("designforextension")
981     public int hashCode()
982     {
983         final int prime = 31;
984         int result = 1;
985         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
986         result = prime * result + this.uniqueNumber;
987         return result;
988     }
989 
990     @Override
991     @SuppressWarnings({"designforextension", "needbraces"})
992     public boolean equals(final Object obj)
993     {
994         if (this == obj)
995             return true;
996         if (obj == null)
997             return false;
998         if (getClass() != obj.getClass())
999             return false;
1000         Gtu other = (Gtu) obj;
1001         if (this.id == null)
1002         {
1003             if (other.id != null)
1004                 return false;
1005         }
1006         else if (!this.id.equals(other.id))
1007             return false;
1008         if (this.uniqueNumber != other.uniqueNumber)
1009             return false;
1010         return true;
1011     }
1012 
1013     
1014 
1015 
1016 
1017     public static EventType MOVE_EVENT = new EventType("GTU.MOVE",
1018             new MetaData("GTU move", "GTU id, position, speed, acceleration, odometer",
1019                     new ObjectDescriptor[] {new ObjectDescriptor("Id", "GTU Id", String.class),
1020                             new ObjectDescriptor("position", "position", PositionVector.class),
1021                             new ObjectDescriptor("direction", "direction", Direction.class),
1022                             new ObjectDescriptor("speed", "speed", Speed.class),
1023                             new ObjectDescriptor("acceleration", "acceleration", Acceleration.class),
1024                             new ObjectDescriptor("Odometer", "Total distance travelled since incarnation", Length.class)}));
1025 
1026     
1027 
1028 
1029 
1030     public static EventType DESTROY_EVENT = new EventType("GTU.DESTROY",
1031             new MetaData("GTU destroy", "GTU id, final position, final odometer",
1032                     new ObjectDescriptor[] {new ObjectDescriptor("Id", "GTU Id", String.class),
1033                             new ObjectDescriptor("position", "position", PositionVector.class),
1034                             new ObjectDescriptor("direction", "direction", Direction.class),
1035                             new ObjectDescriptor("Odometer", "Total distance travelled since incarnation", Length.class)}));
1036 
1037 }