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