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