1 package org.opentrafficsim.core.gtu;
2
3 import java.io.Serializable;
4 import java.util.LinkedHashSet;
5 import java.util.Set;
6
7 import org.djunits.unit.DirectionUnit;
8 import org.djunits.unit.DurationUnit;
9 import org.djunits.unit.PositionUnit;
10 import org.djunits.unit.TimeUnit;
11 import org.djunits.value.vdouble.scalar.Acceleration;
12 import org.djunits.value.vdouble.scalar.Direction;
13 import org.djunits.value.vdouble.scalar.Duration;
14 import org.djunits.value.vdouble.scalar.Length;
15 import org.djunits.value.vdouble.scalar.Speed;
16 import org.djunits.value.vdouble.scalar.Time;
17 import org.djutils.event.EventProducer;
18 import org.djutils.exceptions.Throw;
19 import org.djutils.exceptions.Try;
20 import org.opentrafficsim.base.parameters.ParameterException;
21 import org.opentrafficsim.base.parameters.Parameters;
22 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
23 import org.opentrafficsim.core.geometry.DirectedPoint;
24 import org.opentrafficsim.core.geometry.OTSPoint3D;
25 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
26 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
27 import org.opentrafficsim.core.gtu.plan.strategical.StrategicalPlanner;
28 import org.opentrafficsim.core.gtu.plan.tactical.TacticalPlanner;
29 import org.opentrafficsim.core.idgenerator.IdGenerator;
30 import org.opentrafficsim.core.network.NetworkException;
31 import org.opentrafficsim.core.perception.Historical;
32 import org.opentrafficsim.core.perception.HistoricalValue;
33 import org.opentrafficsim.core.perception.HistoryManager;
34 import org.opentrafficsim.core.perception.PerceivableContext;
35
36 import nl.tudelft.simulation.dsol.SimRuntimeException;
37 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
38
39
40
41
42
43
44
45
46
47
48
49 public abstract class AbstractGTU extends EventProducer implements GTU
50 {
51
52 private static final long serialVersionUID = 20140822L;
53
54
55 private final String id;
56
57
58 private final int uniqueNumber;
59
60
61 private static int staticUNIQUENUMBER = 0;
62
63
64 private final GTUType gtuType;
65
66
67 private final OTSSimulatorInterface simulator;
68
69
70 private Parameters parameters;
71
72
73 private Acceleration maximumAcceleration;
74
75
76 private Acceleration maximumDeceleration;
77
78
79
80
81
82
83 private Historical<Length> odometer;
84
85
86 private final Historical<StrategicalPlanner> strategicalPlanner;
87
88
89 private final Historical<TacticalPlanner<?, ?>> tacticalPlanner;
90
91
92 protected final Historical<OperationalPlan> operationalPlan;
93
94
95 private SimEvent<Duration> nextMoveEvent;
96
97
98 private PerceivableContext perceivableContext;
99
100
101 private boolean destroyed = false;
102
103
104
105 public static boolean ALIGNED = true;
106
107
108
109 public static int ALIGN_COUNT = 0;
110
111
112 private double cachedSpeedTime = Double.NaN;
113
114
115 private Speed cachedSpeed = null;
116
117
118 private double cachedAccelerationTime = Double.NaN;
119
120
121 private Acceleration cachedAcceleration = null;
122
123
124 private GTU parent = null;
125
126
127 private Set<GTU> children = new LinkedHashSet<>();
128
129
130 private GTUErrorHandler errorHandler = GTUErrorHandler.THROW;
131
132
133
134
135
136
137
138
139 @SuppressWarnings("checkstyle:parameternumber")
140 public AbstractGTU(final String id, final GTUType gtuType, final OTSSimulatorInterface simulator,
141 final PerceivableContext perceivableContext) throws GTUException
142 {
143 Throw.when(id == null, GTUException.class, "id is null");
144 Throw.when(gtuType == null, GTUException.class, "gtuType is null");
145 Throw.when(perceivableContext == null, GTUException.class, "perceivableContext is null for GTU with id %s", id);
146 Throw.when(perceivableContext.containsGtuId(id), GTUException.class,
147 "GTU with id %s already registered in perceivableContext %s", id, perceivableContext.getId());
148 Throw.when(simulator == null, GTUException.class, "simulator is null for GTU with id %s", id);
149
150 HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
151 this.id = id;
152 this.uniqueNumber = ++staticUNIQUENUMBER;
153 this.gtuType = gtuType;
154 this.simulator = simulator;
155 this.odometer = new HistoricalValue<>(historyManager, Length.ZERO);
156 this.perceivableContext = perceivableContext;
157 this.perceivableContext.addGTU(this);
158 this.strategicalPlanner = new HistoricalValue<>(historyManager);
159 this.tacticalPlanner = new HistoricalValue<>(historyManager, null);
160 this.operationalPlan = new HistoricalValue<>(historyManager, null);
161 }
162
163
164
165
166
167
168
169
170 @SuppressWarnings("checkstyle:parameternumber")
171 public AbstractGTU(final IdGenerator idGenerator, final GTUType gtuType, final OTSSimulatorInterface simulator,
172 final PerceivableContext perceivableContext) throws GTUException
173 {
174 this(generateId(idGenerator), gtuType, simulator, perceivableContext);
175 }
176
177
178
179
180
181
182
183
184
185
186
187 @SuppressWarnings({"checkstyle:hiddenfield", "hiding", "checkstyle:designforextension"})
188 public void init(final StrategicalPlanner strategicalPlanner, final DirectedPoint initialLocation, final Speed initialSpeed)
189 throws SimRuntimeException, GTUException
190 {
191 Throw.when(strategicalPlanner == null, GTUException.class, "strategicalPlanner is null for GTU with id %s", this.id);
192 Throw.whenNull(initialLocation, "Initial location of GTU cannot be null");
193 Throw.when(Double.isNaN(initialLocation.x) || Double.isNaN(initialLocation.y) || Double.isNaN(initialLocation.z),
194 GTUException.class, "initialLocation %s invalid for GTU with id %s", initialLocation, this.id);
195 Throw.when(initialSpeed == null, GTUException.class, "initialSpeed is null for GTU with id %s", this.id);
196 Throw.when(!getId().equals(strategicalPlanner.getGtu().getId()), GTUException.class,
197 "GTU %s is initialized with a strategical planner for GTU %s", getId(), strategicalPlanner.getGtu().getId());
198
199 this.strategicalPlanner.set(strategicalPlanner);
200 this.tacticalPlanner.set(strategicalPlanner.getTacticalPlanner());
201 Time now = this.simulator.getSimulatorAbsTime();
202
203 DirectedPoint location = getLocation();
204 fireTimedEvent(GTU.INIT_EVENT, new Object[] {getId(), new OTSPoint3D(location).doubleVector(PositionUnit.METER),
205 new Direction(location.getZ(), DirectionUnit.EAST_RADIAN), getLength(), getWidth()}, now);
206
207 try
208 {
209 move(initialLocation);
210 }
211 catch (OperationalPlanException | NetworkException | ParameterException exception)
212 {
213 throw new GTUException("Failed to create OperationalPlan for GTU " + this.id, exception);
214 }
215 }
216
217
218
219
220
221
222
223 private static String generateId(final IdGenerator idGenerator) throws GTUException
224 {
225 Throw.when(idGenerator == null, GTUException.class, "AbstractGTU.<init>: idGenerator is null");
226 return idGenerator.nextId();
227 }
228
229
230
231
232 @Override
233 @SuppressWarnings("checkstyle:designforextension")
234 public void destroy()
235 {
236 DirectedPoint location = getLocation();
237 fireTimedEvent(GTU.DESTROY_EVENT,
238 new Object[] {getId(), new OTSPoint3D(location).doubleVector(PositionUnit.METER),
239 new Direction(location.getZ(), DirectionUnit.EAST_RADIAN), getOdometer()},
240 this.simulator.getSimulatorTime());
241
242
243 if (this.nextMoveEvent != null)
244 {
245 this.simulator.cancelEvent(this.nextMoveEvent);
246 this.nextMoveEvent = null;
247 }
248
249 this.perceivableContext.removeGTU(this);
250 this.destroyed = true;
251 }
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268 @SuppressWarnings("checkstyle:designforextension")
269 protected boolean move(final DirectedPoint fromLocation)
270 throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
271 {
272 try
273 {
274 Time now = this.simulator.getSimulatorAbsTime();
275
276
277
278 Length currentOdometer;
279 if (this.operationalPlan.get() != null)
280 {
281 currentOdometer = this.odometer.get().plus(this.operationalPlan.get().getTraveledDistance(now));
282 }
283 else
284 {
285 currentOdometer = this.odometer.get();
286 }
287
288
289
290 TacticalPlanner<?, ?> tactPlanner = this.tacticalPlanner.get();
291 if (tactPlanner == null)
292 {
293
294 tactPlanner = this.strategicalPlanner.get().getTacticalPlanner();
295 this.tacticalPlanner.set(tactPlanner);
296 }
297 synchronized (this)
298 {
299 tactPlanner.getPerception().perceive();
300 }
301 OperationalPlan newOperationalPlan = tactPlanner.generateOperationalPlan(now, fromLocation);
302 synchronized (this)
303 {
304 this.operationalPlan.set(newOperationalPlan);
305 this.cachedSpeedTime = Double.NaN;
306 this.cachedAccelerationTime = Double.NaN;
307 this.odometer.set(currentOdometer);
308 }
309
310
311 if (ALIGNED && newOperationalPlan.getTotalDuration().si == 0.5)
312 {
313
314
315 double tNext = Math.floor(2.0 * now.si + 1.0) / 2.0;
316 DirectedPoint p = (tNext - now.si < 0.5) ? newOperationalPlan.getEndLocation()
317 : newOperationalPlan.getLocation(new Duration(tNext - now.si, DurationUnit.SI));
318 this.nextMoveEvent = new SimEvent<Duration>(
319 new Duration(tNext - getSimulator().getStartTimeAbs().si, DurationUnit.SI), this, this,
320 "move", new Object[] {p});
321 ALIGN_COUNT++;
322 }
323 else
324 {
325
326
327 this.nextMoveEvent = new SimEvent<>(
328 now.plus(newOperationalPlan.getTotalDuration())
329 .minus(getSimulator().getStartTimeAbs()),
330 this, this, "move", new Object[] {newOperationalPlan.getEndLocation()});
331 }
332 this.simulator.scheduleEvent(this.nextMoveEvent);
333 fireTimedEvent(GTU.MOVE_EVENT,
334 new Object[] {getId(), new OTSPoint3D(fromLocation).doubleVector(PositionUnit.METER),
335 new Direction(fromLocation.getZ(), DirectionUnit.EAST_RADIAN), getSpeed(), getAcceleration(),
336 getOdometer()},
337 this.simulator.getSimulatorTime());
338
339 return false;
340 }
341 catch (Exception ex)
342 {
343 try
344 {
345 this.errorHandler.handle(this, ex);
346 }
347 catch (Exception exception)
348 {
349 throw new GTUException(exception);
350 }
351 return true;
352 }
353 }
354
355
356
357
358
359
360
361
362
363
364
365 @SuppressWarnings("checkstyle:designforextension")
366 protected void interruptMove()
367 throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
368 {
369 this.simulator.cancelEvent(this.nextMoveEvent);
370 move(this.operationalPlan.get().getLocation(this.simulator.getSimulatorTime()));
371 }
372
373
374 @Override
375 public final String getId()
376 {
377 return this.id;
378 }
379
380
381 @SuppressWarnings("checkstyle:designforextension")
382 @Override
383 public GTUType getGTUType()
384 {
385 return this.gtuType;
386 }
387
388
389 @Override
390 public final RelativePosition getReference()
391 {
392 return RelativePosition.REFERENCE_POSITION;
393 }
394
395
396 @Override
397 public final OTSSimulatorInterface getSimulator()
398 {
399 return this.simulator;
400 }
401
402
403 @Override
404 public final Parameters getParameters()
405 {
406 return this.parameters;
407 }
408
409
410 @Override
411 public final void setParameters(final Parameters parameters)
412 {
413 this.parameters = parameters;
414 }
415
416
417 @Override
418 public StrategicalPlanner getStrategicalPlanner()
419 {
420 return this.strategicalPlanner.get();
421 }
422
423
424 @Override
425 public StrategicalPlanner getStrategicalPlanner(final Time time)
426 {
427 return this.strategicalPlanner.get(time);
428 }
429
430
431 @Override
432 public final OperationalPlan getOperationalPlan()
433 {
434 return this.operationalPlan.get();
435 }
436
437
438 @Override
439 public final OperationalPlan getOperationalPlan(final Time time)
440 {
441 return this.operationalPlan.get(time);
442 }
443
444
445 @Override
446 public final Length getOdometer()
447 {
448 return getOdometer(this.simulator.getSimulatorAbsTime());
449 }
450
451
452 @Override
453 public final Length getOdometer(final Time time)
454 {
455 synchronized (this)
456 {
457 if (getOperationalPlan(time) == null)
458 {
459 return this.odometer.get(time);
460 }
461 try
462 {
463 return this.odometer.get(time).plus(getOperationalPlan(time).getTraveledDistance(time));
464 }
465 catch (OperationalPlanException ope)
466 {
467 return this.odometer.get(time);
468 }
469 }
470 }
471
472
473 @Override
474 public final Speed getSpeed()
475 {
476 synchronized (this)
477 {
478 return getSpeed(this.simulator.getSimulatorAbsTime());
479 }
480 }
481
482
483 @Override
484 public final Speed getSpeed(final Time time)
485 {
486 synchronized (this)
487 {
488 if (this.cachedSpeedTime != time.si)
489 {
490
491 this.cachedSpeedTime = Double.NaN;
492 this.cachedSpeed = null;
493 OperationalPlan plan = getOperationalPlan(time);
494 if (plan == null)
495 {
496 this.cachedSpeed = Speed.ZERO;
497 }
498 else if (time.si < plan.getStartTime().si)
499 {
500 this.cachedSpeed = plan.getStartSpeed();
501 }
502 else if (time.si > plan.getEndTime().si)
503 {
504 if (time.si - plan.getEndTime().si < 1e-6)
505 {
506 this.cachedSpeed = Try.assign(() -> plan.getSpeed(plan.getEndTime()),
507 "getSpeed() could not derive a valid speed for the current operationalPlan");
508 }
509 else
510 {
511 throw new IllegalStateException("Requesting speed value beyond plan.");
512 }
513 }
514 else
515 {
516 this.cachedSpeed = Try.assign(() -> plan.getSpeed(time),
517 "getSpeed() could not derive a valid speed for the current operationalPlan");
518 }
519 this.cachedSpeedTime = time.si;
520 }
521 return this.cachedSpeed;
522 }
523 }
524
525
526 @Override
527 public final Acceleration getAcceleration()
528 {
529 synchronized (this)
530 {
531 return getAcceleration(this.simulator.getSimulatorAbsTime());
532 }
533 }
534
535
536 @Override
537 public final Acceleration getAcceleration(final Time time)
538 {
539 synchronized (this)
540 {
541 if (this.cachedAccelerationTime != time.si)
542 {
543
544 this.cachedAccelerationTime = Double.NaN;
545 this.cachedAcceleration = null;
546 OperationalPlan plan = getOperationalPlan(time);
547 if (plan == null)
548 {
549 this.cachedAcceleration = Acceleration.ZERO;
550 }
551 else if (time.si < plan.getStartTime().si)
552 {
553 this.cachedAcceleration =
554 Try.assign(() -> plan.getAcceleration(plan.getStartTime()), "Exception obtaining acceleration.");
555 }
556 else if (time.si > plan.getEndTime().si)
557 {
558 if (time.si - plan.getEndTime().si < 1e-6)
559 {
560 this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(plan.getEndTime()),
561 "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
562 }
563 else
564 {
565 throw new IllegalStateException("Requesting acceleration value beyond plan.");
566 }
567 }
568 else
569 {
570 this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(time),
571 "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
572 }
573 this.cachedAccelerationTime = time.si;
574 }
575 return this.cachedAcceleration;
576 }
577 }
578
579
580
581
582 @Override
583 public final Acceleration getMaximumAcceleration()
584 {
585 return this.maximumAcceleration;
586 }
587
588
589
590
591 public final void setMaximumAcceleration(final Acceleration maximumAcceleration)
592 {
593 if (maximumAcceleration.le(Acceleration.ZERO))
594 {
595 throw new RuntimeException("Maximum acceleration of GTU " + this.id + " set to value <= 0");
596 }
597 this.maximumAcceleration = maximumAcceleration;
598 }
599
600
601
602
603 @Override
604 public final Acceleration getMaximumDeceleration()
605 {
606 return this.maximumDeceleration;
607 }
608
609
610
611
612
613 public final void setMaximumDeceleration(final Acceleration maximumDeceleration)
614 {
615 if (maximumDeceleration.ge(Acceleration.ZERO))
616 {
617 throw new RuntimeException("Cannot set maximum deceleration of GTU " + this.id + " to " + maximumDeceleration
618 + " (value must be negative)");
619 }
620 this.maximumDeceleration = maximumDeceleration;
621 }
622
623
624 private Time cacheLocationTime = new Time(Double.NaN, TimeUnit.DEFAULT);
625
626
627 private DirectedPoint cacheLocation = null;
628
629
630 @Override
631 @SuppressWarnings("checkstyle:designforextension")
632 public DirectedPoint getLocation()
633 {
634 synchronized (this)
635 {
636 if (this.operationalPlan.get() == null)
637 {
638 this.simulator.getLogger().always()
639 .error("No operational plan for GTU " + this.id + " at t=" + this.getSimulator().getSimulatorTime());
640 return new DirectedPoint(0, 0, 0);
641 }
642 try
643 {
644
645 Time locationTime = this.simulator.getSimulatorAbsTime();
646 if (null == this.cacheLocationTime || this.cacheLocationTime.si != locationTime.si)
647 {
648 this.cacheLocationTime = null;
649 this.cacheLocation = this.operationalPlan.get().getLocation(locationTime);
650 this.cacheLocationTime = locationTime;
651 }
652 return this.cacheLocation;
653 }
654 catch (OperationalPlanException exception)
655 {
656 return new DirectedPoint(0, 0, 0);
657 }
658 }
659 }
660
661
662 @Override
663 public final boolean isDestroyed()
664 {
665 return this.destroyed;
666 }
667
668
669 @Override
670 public PerceivableContext getPerceivableContext()
671 {
672 return this.perceivableContext;
673 }
674
675
676 @Override
677 public void addGtu(final GTU gtu) throws GTUException
678 {
679 this.children.add(gtu);
680 gtu.setParent(this);
681 }
682
683
684 @Override
685 public void removeGtu(final GTU gtu)
686 {
687 this.children.remove(gtu);
688 try
689 {
690 gtu.setParent(null);
691 }
692 catch (GTUException exception)
693 {
694
695 }
696 }
697
698
699 @Override
700 public void setParent(final GTU gtu) throws GTUException
701 {
702 Throw.when(gtu != null && this.parent != null, GTUException.class, "GTU %s already has a parent.", this);
703 this.parent = gtu;
704 }
705
706
707 @Override
708 public GTU getParent()
709 {
710 return this.parent;
711 }
712
713
714 @Override
715 public Set<GTU> getChildren()
716 {
717 return new LinkedHashSet<>(this.children);
718 }
719
720
721
722
723 protected GTUErrorHandler getErrorHandler()
724 {
725 return this.errorHandler;
726 }
727
728
729 @Override
730 public void setErrorHandler(final GTUErrorHandler errorHandler)
731 {
732 this.errorHandler = errorHandler;
733 }
734
735
736 @Override
737 public final Serializable getSourceId()
738 {
739 return this;
740 }
741
742
743
744
745
746 public final SimEvent<Duration> getNextMoveEvent()
747 {
748 return this.nextMoveEvent;
749 }
750
751
752 @Override
753 @SuppressWarnings("designforextension")
754 public int hashCode()
755 {
756 final int prime = 31;
757 int result = 1;
758 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
759 result = prime * result + this.uniqueNumber;
760 return result;
761 }
762
763
764 @Override
765 @SuppressWarnings({"designforextension", "needbraces"})
766 public boolean equals(final Object obj)
767 {
768 if (this == obj)
769 return true;
770 if (obj == null)
771 return false;
772 if (getClass() != obj.getClass())
773 return false;
774 AbstractGTU other = (AbstractGTU) obj;
775 if (this.id == null)
776 {
777 if (other.id != null)
778 return false;
779 }
780 else if (!this.id.equals(other.id))
781 return false;
782 if (this.uniqueNumber != other.uniqueNumber)
783 return false;
784 return true;
785 }
786
787 }