1 package org.opentrafficsim.core.gtu.plan.operational;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Iterator;
7 import java.util.List;
8
9 import org.djunits.unit.AccelerationUnit;
10 import org.djunits.unit.DurationUnit;
11 import org.djunits.unit.LengthUnit;
12 import org.djunits.unit.SpeedUnit;
13 import org.djunits.unit.TimeUnit;
14 import org.djunits.value.vdouble.scalar.Acceleration;
15 import org.djunits.value.vdouble.scalar.Duration;
16 import org.djunits.value.vdouble.scalar.Length;
17 import org.djunits.value.vdouble.scalar.Speed;
18 import org.djunits.value.vdouble.scalar.Time;
19 import org.djutils.exceptions.Throw;
20 import org.opentrafficsim.core.geometry.OTSGeometryException;
21 import org.opentrafficsim.core.geometry.OTSLine3D;
22 import org.opentrafficsim.core.geometry.OTSPoint3D;
23 import org.opentrafficsim.core.gtu.GTU;
24 import org.opentrafficsim.core.gtu.RelativePosition;
25 import org.opentrafficsim.core.math.Solver;
26
27 import nl.tudelft.simulation.dsol.logger.SimLogger;
28 import nl.tudelft.simulation.language.d3.DirectedPoint;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 public class OperationalPlan implements Serializable
46 {
47
48 private static final long serialVersionUID = 20151114L;
49
50
51 private final OTSLine3D path;
52
53
54 private final Time startTime;
55
56
57 private final Speed startSpeed;
58
59
60 private final List<OperationalPlan.Segment> operationalPlanSegmentList;
61
62
63 private final Duration totalDuration;
64
65
66 private final Length totalLength;
67
68
69 private final Speed endSpeed;
70
71
72 private final boolean waitPlan;
73
74
75 private final GTU gtu;
76
77
78
79
80
81 private final double[] segmentStartTimesRelSI;
82
83
84
85
86
87 static final double MAX_DELTA_SI = 1E-6;
88
89
90 public static final double DRIFTING_SPEED_SI = 1E-3;
91
92
93
94
95
96
97
98
99
100
101
102
103 public OperationalPlan(final GTU gtu, final OTSLine3D path, final Time startTime, final Speed startSpeed,
104 final List<Segment> operationalPlanSegmentList) throws OperationalPlanException
105 {
106
107 this.waitPlan = false;
108 this.gtu = gtu;
109 this.startTime = startTime;
110 this.startSpeed = startSpeed;
111 this.operationalPlanSegmentList = operationalPlanSegmentList;
112 this.segmentStartTimesRelSI = new double[this.operationalPlanSegmentList.size() + 1];
113
114
115 Speed v0 = this.startSpeed;
116 double distanceSI = 0.0;
117 double durationSI = 0.0;
118 for (int i = 0; i < this.operationalPlanSegmentList.size(); i++)
119 {
120 Segment segment = this.operationalPlanSegmentList.get(i);
121 if (Math.abs(v0.si) < DRIFTING_SPEED_SI && segment.accelerationSI(0.0) == 0.0)
122 {
123 v0 = Speed.ZERO;
124 }
125 segment.setV0(v0);
126 this.segmentStartTimesRelSI[i] = durationSI;
127 distanceSI += segment.distanceSI();
128 v0 = segment.endSpeed();
129 durationSI += segment.getDuration().si;
130 }
131 this.segmentStartTimesRelSI[this.segmentStartTimesRelSI.length - 1] = durationSI;
132 try
133 {
134 this.path = path.extract(0.0, Math.min(distanceSI, path.getLengthSI()));
135 }
136 catch (OTSGeometryException exception)
137 {
138 throw new OperationalPlanException(exception);
139 }
140 this.totalDuration = new Duration(durationSI, DurationUnit.SI);
141 this.totalLength = new Length(distanceSI, LengthUnit.SI);
142 this.endSpeed = v0;
143
144
145
146
147
148
149 }
150
151
152
153
154
155
156
157
158
159 public OperationalPlan(final GTU gtu, final DirectedPoint waitPoint, final Time startTime, final Duration duration)
160 throws OperationalPlanException
161 {
162 this.waitPlan = true;
163 this.gtu = gtu;
164 this.startTime = startTime;
165 this.startSpeed = Speed.ZERO;
166 this.endSpeed = Speed.ZERO;
167 this.totalDuration = duration;
168 this.totalLength = Length.ZERO;
169
170
171 OTSPoint3D p2 = new OTSPoint3D(waitPoint.x + Math.cos(waitPoint.getRotZ()), waitPoint.y + Math.sin(waitPoint.getRotZ()),
172 waitPoint.z);
173 try
174 {
175 this.path = new OTSLine3D(new OTSPoint3D(waitPoint), p2);
176 }
177 catch (OTSGeometryException exception)
178 {
179 throw new OperationalPlanException(exception);
180 }
181
182 this.operationalPlanSegmentList = new ArrayList<>();
183 Segment segment = new SpeedSegment(duration);
184 segment.setV0(Speed.ZERO);
185 this.operationalPlanSegmentList.add(segment);
186 this.segmentStartTimesRelSI = new double[2];
187 this.segmentStartTimesRelSI[0] = 0.0;
188 this.segmentStartTimesRelSI[1] = duration.si;
189 }
190
191
192
193
194
195
196 public final OTSLine3D getPath()
197 {
198 return this.path;
199 }
200
201
202
203
204
205 public final Time getStartTime()
206 {
207 return this.startTime;
208 }
209
210
211
212
213
214 public final Speed getStartSpeed()
215 {
216 return this.startSpeed;
217 }
218
219
220
221
222 public final Speed getEndSpeed()
223 {
224 return this.endSpeed;
225 }
226
227
228
229
230
231
232 public final List<OperationalPlan.Segment> getOperationalPlanSegmentList()
233 {
234 return this.operationalPlanSegmentList;
235 }
236
237
238
239
240
241 public final Duration getTotalDuration()
242 {
243 return this.totalDuration;
244 }
245
246
247
248
249
250 public final Length getTotalLength()
251 {
252 return this.totalLength;
253 }
254
255
256
257
258
259 public final Time getEndTime()
260 {
261 return this.startTime.plus(this.totalDuration);
262 }
263
264
265
266
267
268 public final DirectedPoint getEndLocation()
269 {
270 try
271 {
272 if (this.waitPlan)
273 {
274 return this.path.getLocationFraction(0.0);
275 }
276 else
277 {
278 return this.path.getLocationFraction(1.0);
279 }
280 }
281 catch (OTSGeometryException exception)
282 {
283
284 throw new RuntimeException(exception);
285 }
286 }
287
288
289
290
291
292
293
294
295
296
297
298
299 private class SegmentProgress implements Serializable
300 {
301
302 private static final long serialVersionUID = 20160000L;
303
304
305 private final Segment segment;
306
307
308 private final Time segmentStartTime;
309
310
311 private final Length segmentStartPosition;
312
313
314
315
316
317
318
319 SegmentProgress(final Segment segment, final Time segmentStartTime, final Length segmentStartPosition)
320 {
321 this.segment = segment;
322 this.segmentStartTime = segmentStartTime;
323 this.segmentStartPosition = segmentStartPosition;
324 }
325
326
327
328
329
330 public final Segment getSegment()
331 {
332 return this.segment;
333 }
334
335
336
337
338
339 public final Time getSegmentStartTime()
340 {
341 return this.segmentStartTime;
342 }
343
344
345
346
347
348 public final Length getSegmentStartPosition()
349 {
350 return this.segmentStartPosition;
351 }
352
353
354 @Override
355 public final String toString()
356 {
357 return String.format("SegmentProgress segment=%s startpos.rel=%s starttime.abs=%s", this.segment,
358 this.segmentStartPosition, this.segmentStartTime);
359 }
360 }
361
362
363
364
365
366
367
368
369 private SegmentProgress getSegmentProgress(final Time time) throws OperationalPlanException
370 {
371 if (time.lt(this.startTime))
372 {
373 throw new OperationalPlanException(
374 this.gtu + ", t = " + time + "SegmentProgress cannot be determined for time before startTime "
375 + getStartTime() + " of this OperationalPlan");
376 }
377 double cumulativeDistance = 0;
378 for (int i = 0; i < this.segmentStartTimesRelSI.length - 1; i++)
379 {
380 if (this.startTime.si + this.segmentStartTimesRelSI[i + 1] >= time.si)
381 {
382 return new SegmentProgress(this.operationalPlanSegmentList.get(i),
383 new Time(this.startTime.si + this.segmentStartTimesRelSI[i], TimeUnit.BASE),
384 new Length(cumulativeDistance, LengthUnit.SI));
385 }
386 cumulativeDistance += this.operationalPlanSegmentList.get(i).distanceSI();
387 }
388 throw new OperationalPlanException(this.gtu + ", t = " + time
389 + " SegmentProgress cannot be determined for time after endTime " + getEndTime() + " of this OperationalPlan");
390 }
391
392
393
394
395
396
397 public final Time timeAtDistance(final Length distance)
398 {
399 Throw.when(getTotalLength().lt(distance), RuntimeException.class, "Requesting %s from a plan with length %s", distance,
400 getTotalLength());
401 double remainingDistanceSI = distance.si;
402 double timeAtStartOfSegment = this.startTime.si;
403 Iterator<Segment> it = this.operationalPlanSegmentList.iterator();
404 while (it.hasNext() && remainingDistanceSI >= 0.0)
405 {
406 Segment segment = it.next();
407 double distanceOfSegment = segment.distanceSI();
408 if (distanceOfSegment > remainingDistanceSI)
409 {
410 return new Time(timeAtStartOfSegment + segment.timeAtDistance(Length.createSI(remainingDistanceSI)).si,
411 TimeUnit.BASE);
412 }
413 remainingDistanceSI -= distanceOfSegment;
414 timeAtStartOfSegment += segment.getDurationSI();
415 }
416 return new Time(Double.NaN, TimeUnit.BASE);
417 }
418
419
420
421
422
423
424
425 public final DirectedPoint getLocation(final Time time) throws OperationalPlanException
426 {
427 SegmentProgress sp = getSegmentProgress(time);
428 Segment segment = sp.getSegment();
429 Duration deltaT = time.minus(sp.getSegmentStartTime());
430 double distanceTraveledInSegment = segment.distanceSI(deltaT.si);
431 double startDistance = sp.getSegmentStartPosition().si;
432 double fraction = (startDistance + distanceTraveledInSegment) / this.path.getLengthSI();
433 DirectedPoint p = new DirectedPoint();
434 try
435 {
436 p = this.path.getLocationFraction(fraction, 0.01);
437 }
438 catch (OTSGeometryException exception)
439 {
440 SimLogger.always().error("OperationalPlan.getLocation(): " + exception.getMessage());
441 p = this.path.getLocationFractionExtended(fraction);
442 }
443 p.setZ(p.getZ() + 0.001);
444 return p;
445 }
446
447
448
449
450
451
452
453 public final Speed getSpeed(final Duration time) throws OperationalPlanException
454 {
455 return getSpeed(time.plus(this.startTime));
456 }
457
458
459
460
461
462
463
464 public final Speed getSpeed(final Time time) throws OperationalPlanException
465 {
466 SegmentProgress sp = getSegmentProgress(time);
467 return new Speed(sp.getSegment().speedSI(time.minus(sp.getSegmentStartTime()).si), SpeedUnit.SI);
468 }
469
470
471
472
473
474
475
476 public final Acceleration getAcceleration(final Duration time) throws OperationalPlanException
477 {
478 return getAcceleration(time.plus(this.startTime));
479 }
480
481
482
483
484
485
486
487 public final Acceleration getAcceleration(final Time time) throws OperationalPlanException
488 {
489 SegmentProgress sp = getSegmentProgress(time);
490 return new Acceleration(sp.getSegment().accelerationSI(time.minus(sp.getSegmentStartTime()).si), AccelerationUnit.SI);
491 }
492
493
494
495
496
497
498
499 public final DirectedPoint getLocation(final Duration time) throws OperationalPlanException
500 {
501 double distanceSI = getTraveledDistanceSI(time);
502 return this.path.getLocationExtendedSI(distanceSI);
503 }
504
505
506
507
508
509
510
511
512 public final DirectedPoint getLocation(final Time time, final RelativePosition pos) throws OperationalPlanException
513 {
514 double distanceSI = getTraveledDistanceSI(time) + pos.getDx().si;
515 return this.path.getLocationExtendedSI(distanceSI);
516 }
517
518
519
520
521
522
523
524
525 public final double getTraveledDistanceSI(final Duration duration) throws OperationalPlanException
526 {
527 return getTraveledDistanceSI(this.startTime.plus(duration));
528 }
529
530
531
532
533
534
535
536 public final Length getTraveledDistance(final Duration duration) throws OperationalPlanException
537 {
538 return new Length(getTraveledDistanceSI(duration), LengthUnit.SI);
539 }
540
541
542
543
544
545
546
547
548 public final double getTraveledDistanceSI(final Time time) throws OperationalPlanException
549 {
550 Throw.when(time.lt(this.getStartTime()), OperationalPlanException.class,
551 "getTravelDistance exception: requested traveled distance before start of plan");
552 Throw.when(time.si > this.getEndTime().si + 1e-6, OperationalPlanException.class,
553 "getTravelDistance exception: requested traveled distance beyond end of plan");
554 if (this.operationalPlanSegmentList.size() == 1)
555 {
556 return this.operationalPlanSegmentList.get(0).distanceSI(time.si - this.startTime.si);
557 }
558 SegmentProgress sp = getSegmentProgress(time);
559 return sp.getSegmentStartPosition().si + sp.getSegment().distanceSI(time.minus(sp.getSegmentStartTime()).si);
560 }
561
562
563
564
565
566
567
568
569
570 public final Time timeAtPoint(final DirectedPoint point, final boolean upstream)
571 {
572 OTSPoint3D p1 = new OTSPoint3D(point);
573
574 OTSPoint3D p2 = new OTSPoint3D(point.x - Math.sin(point.getRotZ()), point.y + Math.cos(point.getRotZ()), point.z);
575 double traveledDistanceAlongPath = 0.0;
576 try
577 {
578 if (upstream)
579 {
580 OTSPoint3D p = OTSPoint3D.intersectionOfLines(this.path.get(0), this.path.get(1), p1, p2);
581 double dist = traveledDistanceAlongPath - this.path.get(0).distance(p).si;
582 dist = dist >= 0.0 ? dist : 0.0;
583 return timeAtDistance(Length.createSI(dist));
584 }
585 for (int i = 0; i < this.path.size() - 1; i++)
586 {
587 OTSPoint3D prevPoint = this.path.get(i);
588 OTSPoint3D nextPoint = this.path.get(i + 1);
589 OTSPoint3D p = OTSPoint3D.intersectionOfLines(prevPoint, nextPoint, p1, p2);
590 if (p == null)
591 {
592
593 continue;
594 }
595 boolean onSegment =
596 prevPoint.distanceSI(nextPoint) + 2e-5 > Math.max(prevPoint.distanceSI(p), nextPoint.distanceSI(p));
597
598
599
600
601
602
603
604 if (p != null
605 && (i == this.path.size() - 2 || onSegment))
606 {
607
608 traveledDistanceAlongPath += this.path.get(i).distance(p).si;
609 if (traveledDistanceAlongPath > this.path.getLengthSI())
610 {
611 return Time.createSI(getEndTime().si - 1e-9);
612 }
613 return timeAtDistance(Length.createSI(traveledDistanceAlongPath));
614 }
615 else
616 {
617 traveledDistanceAlongPath += this.path.get(i).distance(this.path.get(i + 1)).si;
618 }
619 }
620 }
621 catch (OTSGeometryException exception)
622 {
623 throw new RuntimeException("Index out of bounds on projection of point to path of operational plan", exception);
624 }
625 SimLogger.always().error("timeAtPoint failed");
626 return null;
627 }
628
629
630
631
632
633
634
635 public final Length getTraveledDistance(final Time time) throws OperationalPlanException
636 {
637 return new Length(getTraveledDistanceSI(time.minus(this.startTime)), LengthUnit.SI);
638 }
639
640
641 @SuppressWarnings("checkstyle:designforextension")
642 @Override
643 public int hashCode()
644 {
645 final int prime = 31;
646 int result = 1;
647 result = prime * result + ((this.operationalPlanSegmentList == null) ? 0 : this.operationalPlanSegmentList.hashCode());
648 result = prime * result + ((this.path == null) ? 0 : this.path.hashCode());
649 result = prime * result + ((this.startSpeed == null) ? 0 : this.startSpeed.hashCode());
650 result = prime * result + ((this.startTime == null) ? 0 : this.startTime.hashCode());
651 return result;
652 }
653
654
655 @SuppressWarnings({"checkstyle:needbraces", "checkstyle:designforextension"})
656 @Override
657 public boolean equals(final Object obj)
658 {
659 if (this == obj)
660 return true;
661 if (obj == null)
662 return false;
663 if (getClass() != obj.getClass())
664 return false;
665 OperationalPlan other = (OperationalPlan) obj;
666 if (this.operationalPlanSegmentList == null)
667 {
668 if (other.operationalPlanSegmentList != null)
669 return false;
670 }
671 else if (!this.operationalPlanSegmentList.equals(other.operationalPlanSegmentList))
672 return false;
673 if (this.path == null)
674 {
675 if (other.path != null)
676 return false;
677 }
678 else if (!this.path.equals(other.path))
679 return false;
680 if (this.startSpeed == null)
681 {
682 if (other.startSpeed != null)
683 return false;
684 }
685 else if (!this.startSpeed.equals(other.startSpeed))
686 return false;
687 if (this.startTime == null)
688 {
689 if (other.startTime != null)
690 return false;
691 }
692 else if (!this.startTime.equals(other.startTime))
693 return false;
694 return true;
695 }
696
697
698 @SuppressWarnings("checkstyle:designforextension")
699 @Override
700 public String toString()
701 {
702 return "OperationalPlan [path=" + this.path + ", startTime=" + this.startTime + ", startSpeed=" + this.startSpeed
703 + ", operationalPlanSegmentList=" + this.operationalPlanSegmentList + ", totalDuration=" + this.totalDuration
704 + ", segmentStartTimesSI=" + Arrays.toString(this.segmentStartTimesRelSI) + ", endSpeed = " + this.endSpeed
705 + "]";
706 }
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725 public abstract static class Segment implements Serializable
726 {
727
728 private static final long serialVersionUID = 20151114L;
729
730
731 @SuppressWarnings("checkstyle:visibilitymodifier")
732 protected final Duration duration;
733
734
735 @SuppressWarnings("checkstyle:visibilitymodifier")
736 protected Speed v0;
737
738
739
740
741 public Segment(final Duration duration)
742 {
743 super();
744 this.duration = duration;
745 }
746
747
748
749
750 final void setV0(final Speed v0)
751 {
752 this.v0 = v0;
753 }
754
755
756
757
758 public final Duration getDuration()
759 {
760 return this.duration;
761 }
762
763
764
765
766 public final double getDurationSI()
767 {
768 return this.duration.si;
769 }
770
771
772
773
774
775 final double distanceSI()
776 {
777 return distanceSI(getDuration().si);
778 }
779
780
781
782
783
784
785 abstract double distanceSI(double t);
786
787
788
789
790
791
792 abstract double speedSI(double t);
793
794
795
796
797
798
799 abstract double accelerationSI(double t);
800
801
802
803
804
805 abstract Speed endSpeed();
806
807
808
809
810
811
812
813 abstract Duration timeAtDistance(final Length distance);
814
815
816 @SuppressWarnings("checkstyle:designforextension")
817 @Override
818 public int hashCode()
819 {
820 final int prime = 31;
821 int result = 1;
822 result = prime * result + ((this.duration == null) ? 0 : this.duration.hashCode());
823 result = prime * result + ((this.v0 == null) ? 0 : this.v0.hashCode());
824 return result;
825 }
826
827
828 @SuppressWarnings({"checkstyle:needbraces", "checkstyle:designforextension"})
829 @Override
830 public boolean equals(final Object obj)
831 {
832 if (this == obj)
833 return true;
834 if (obj == null)
835 return false;
836 if (getClass() != obj.getClass())
837 return false;
838 Segment other = (Segment) obj;
839 if (this.duration == null)
840 {
841 if (other.duration != null)
842 return false;
843 }
844 else if (!this.duration.equals(other.duration))
845 return false;
846 if (this.v0 == null)
847 {
848 if (other.v0 != null)
849 return false;
850 }
851 else if (!this.v0.equals(other.v0))
852 return false;
853 return true;
854 }
855 }
856
857
858
859
860
861
862
863
864
865
866
867
868
869 public static class AccelerationSegment extends Segment
870 {
871
872 private static final long serialVersionUID = 20151114L;
873
874
875 private final Acceleration acceleration;
876
877
878
879
880
881 public AccelerationSegment(final Duration duration, final Acceleration acceleration)
882 {
883 super(duration);
884 this.acceleration = acceleration;
885 }
886
887
888 @Override
889 final double distanceSI(final double t)
890 {
891 return this.v0.si * t + 0.5 * this.acceleration.si * t * t;
892 }
893
894
895 @Override
896 final double accelerationSI(final double t)
897 {
898 return this.acceleration.si;
899 }
900
901
902 @Override
903 final double speedSI(final double t)
904 {
905 return this.v0.si + this.acceleration.si * t;
906 }
907
908
909 @Override
910 final Speed endSpeed()
911 {
912 return this.v0.plus(this.acceleration.multiplyBy(getDuration()));
913 }
914
915
916 @Override
917 public final int hashCode()
918 {
919 final int prime = 31;
920 int result = super.hashCode();
921 result = prime * result + ((this.acceleration == null) ? 0 : this.acceleration.hashCode());
922 return result;
923 }
924
925
926 @Override
927 @SuppressWarnings("checkstyle:needbraces")
928 public final boolean equals(final Object obj)
929 {
930 if (this == obj)
931 return true;
932 if (!super.equals(obj))
933 return false;
934 if (getClass() != obj.getClass())
935 return false;
936 AccelerationSegment other = (AccelerationSegment) obj;
937 if (this.acceleration == null)
938 {
939 if (other.acceleration != null)
940 return false;
941 }
942 else if (!this.acceleration.equals(other.acceleration))
943 return false;
944 return true;
945 }
946
947
948 @Override
949 public final Duration timeAtDistance(final Length distance)
950 {
951 double[] solutions = Solver.solve(this.acceleration.si / 2, this.v0.si, -distance.si);
952
953 for (double solution : solutions)
954 {
955 if (solution >= 0 && solution <= this.duration.si)
956 {
957 return new Duration(solution, DurationUnit.SI);
958 }
959 }
960 SimLogger.always().error("AccelerationSegment " + this + " timeAtDistance( " + distance + ") failed");
961 return null;
962 }
963
964
965 @Override
966 public final String toString()
967 {
968 return "AccelerationSegment [t=" + this.duration + ", v0=" + this.v0 + ", a=" + this.acceleration + "]";
969 }
970
971 }
972
973
974
975
976
977
978
979
980
981
982
983
984
985 public static class SpeedSegment extends Segment
986 {
987
988 private static final long serialVersionUID = 20151114L;
989
990
991
992
993 public SpeedSegment(final Duration duration)
994 {
995 super(duration);
996 }
997
998
999 @Override
1000 final double distanceSI(final double t)
1001 {
1002 return this.v0.si * t;
1003 }
1004
1005
1006 @Override
1007 final double accelerationSI(final double t)
1008 {
1009 return 0.0;
1010 }
1011
1012
1013 @Override
1014 final double speedSI(final double t)
1015 {
1016 return this.v0.si;
1017 }
1018
1019
1020 @Override
1021 final Speed endSpeed()
1022 {
1023 return this.v0;
1024 }
1025
1026
1027
1028
1029 public final Speed getSpeed()
1030 {
1031 return this.v0;
1032 }
1033
1034
1035 @Override
1036 public final Duration timeAtDistance(final Length distance)
1037 {
1038 double[] solution = Solver.solve(this.v0.si, -distance.si);
1039 if (solution.length > 0 && solution[0] >= 0 && solution[0] <= getDurationSI())
1040 {
1041 return new Duration(solution[0], DurationUnit.SI);
1042 }
1043 SimLogger.always().error("SpeedSegment " + this + " timeAtDistance( " + distance + ") failed");
1044 return null;
1045 }
1046
1047
1048 @Override
1049 public final String toString()
1050 {
1051 return "SpeedSegment [t=" + this.duration + ", v0=" + this.v0 + "]";
1052 }
1053
1054 }
1055
1056 }