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