1 package org.opentrafficsim.road.gtu.generator;
2
3 import java.rmi.RemoteException;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.LinkedHashMap;
10 import java.util.LinkedHashSet;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Set;
14
15 import javax.media.j3d.BoundingBox;
16 import javax.media.j3d.Bounds;
17 import javax.vecmath.Point3d;
18
19 import org.djunits.unit.SpeedUnit;
20 import org.djunits.value.vdouble.scalar.Length;
21 import org.djunits.value.vdouble.scalar.Speed;
22 import org.djutils.exceptions.Throw;
23 import org.opentrafficsim.core.geometry.OTSGeometryException;
24 import org.opentrafficsim.core.gtu.GTUDirectionality;
25 import org.opentrafficsim.core.gtu.GTUType;
26 import org.opentrafficsim.core.math.Draw;
27 import org.opentrafficsim.core.network.Link;
28 import org.opentrafficsim.core.network.LinkDirection;
29 import org.opentrafficsim.core.network.NetworkException;
30 import org.opentrafficsim.core.network.route.Route;
31 import org.opentrafficsim.road.gtu.generator.GeneratorPositions.RoadPosition.BySpeed;
32 import org.opentrafficsim.road.gtu.generator.GeneratorPositions.RoadPosition.ByValue;
33 import org.opentrafficsim.road.network.lane.CrossSectionLink;
34 import org.opentrafficsim.road.network.lane.DirectedLanePosition;
35 import org.opentrafficsim.road.network.lane.Lane;
36
37 import nl.tudelft.simulation.dsol.animation.Locatable;
38 import nl.tudelft.simulation.jstats.streams.StreamInterface;
39 import nl.tudelft.simulation.language.d3.DirectedPoint;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public final class GeneratorPositions implements Locatable
55 {
56
57
58 private final GeneratorZonePosition position;
59
60
61 private final LaneBiases biases;
62
63
64 private final StreamInterface stream;
65
66
67 private final DirectedPoint location;
68
69
70 private final Bounds bounds;
71
72
73 private final Set<GeneratorLanePosition> allPositions = new HashSet<>();
74
75
76
77
78
79
80
81 @SuppressWarnings("synthetic-access")
82 private GeneratorPositions(final GeneratorZonePosition position, final LaneBiases biases, final StreamInterface stream)
83 {
84 this.position = position;
85 this.biases = biases;
86 this.stream = stream;
87 double x = 0.0;
88 double y = 0.0;
89 double xMin = Double.POSITIVE_INFINITY;
90 double xMax = Double.NEGATIVE_INFINITY;
91 double yMin = Double.POSITIVE_INFINITY;
92 double yMax = Double.NEGATIVE_INFINITY;
93 int n = 0;
94 for (GeneratorLinkPosition linkPosition : position.positions)
95 {
96 for (GeneratorLanePosition lanePosition : linkPosition.positions)
97 {
98 this.allPositions.add(lanePosition);
99 for (DirectedLanePosition pos : lanePosition.getPosition())
100 {
101 DirectedPoint point;
102 try
103 {
104 point = pos.getLane().getCenterLine().getLocation(pos.getPosition());
105 }
106 catch (OTSGeometryException exception)
107 {
108 point = new DirectedPoint(0, 0, 0);
109 }
110 x += point.x;
111 y += point.y;
112 xMin = xMin < point.x ? xMin : point.x;
113 yMin = yMin < point.y ? yMin : point.y;
114 xMax = xMax > point.x ? xMax : point.x;
115 yMax = yMax > point.y ? yMax : point.y;
116 n++;
117 }
118 }
119 }
120 this.location = new DirectedPoint(x / n, y / n, 0);
121 this.bounds = new BoundingBox(new Point3d(xMin, yMin, 0.0), new Point3d(xMax, yMax, 0.0));
122 }
123
124
125
126
127
128
129
130
131 public static GeneratorPositions create(final Set<DirectedLanePosition> positions, final StreamInterface stream)
132 {
133 return create(positions, stream, null, null);
134 }
135
136
137
138
139
140
141
142
143
144 public static GeneratorPositions create(final Set<DirectedLanePosition> positions, final StreamInterface stream,
145 final LaneBiases biases)
146 {
147 return create(positions, stream, biases, null);
148 }
149
150
151
152
153
154
155
156
157
158 public static GeneratorPositions create(final Set<DirectedLanePosition> positions, final StreamInterface stream,
159 final Map<CrossSectionLink, Double> linkWeights)
160 {
161 return create(positions, stream, null, linkWeights);
162 }
163
164
165
166
167
168
169
170
171
172 public static GeneratorPositions create(final Set<DirectedLanePosition> positions, final StreamInterface stream,
173 final LaneBiases biases, final Map<CrossSectionLink, Double> linkWeights)
174 {
175
176
177 Map<LinkDirection, Set<DirectedLanePosition>> linkSplit = new LinkedHashMap<>();
178 for (DirectedLanePosition position : positions)
179 {
180 if (!linkSplit.containsKey(position.getLinkDirection()))
181 {
182 linkSplit.put(position.getLinkDirection(), new LinkedHashSet<>());
183 }
184 linkSplit.get(position.getLinkDirection()).add(position);
185 }
186
187
188 List<GeneratorLinkPosition> linkPositions = new ArrayList<>();
189 for (LinkDirection linkDirection : linkSplit.keySet())
190 {
191 List<Lane> lanes = ((CrossSectionLink) linkDirection.getLink()).getLanes();
192
193 Collections.sort(lanes, new Comparator<Lane>()
194 {
195
196 @Override
197 public int compare(final Lane lane1, final Lane lane2)
198 {
199 Length lat1 = linkDirection.getDirection().isPlus() ? lane1.getDesignLineOffsetAtBegin()
200 : lane1.getDesignLineOffsetAtEnd().neg();
201 Length lat2 = linkDirection.getDirection().isPlus() ? lane2.getDesignLineOffsetAtBegin()
202 : lane2.getDesignLineOffsetAtEnd().neg();
203 return lat1.compareTo(lat2);
204 }
205 });
206
207 List<GeneratorLanePosition> lanePositions = new ArrayList<>();
208 for (DirectedLanePosition lanePosition : linkSplit.get(linkDirection))
209 {
210 Set<DirectedLanePosition> set = new LinkedHashSet<>();
211 set.add(lanePosition);
212 lanePositions.add(new GeneratorLanePosition(lanes.indexOf(lanePosition.getLane()) + 1, set,
213 (CrossSectionLink) linkDirection.getLink()));
214 }
215
216 CrossSectionLink link = (CrossSectionLink) linkDirection.getLink();
217 if (linkWeights == null)
218 {
219 linkPositions.add(new GeneratorLinkPosition(lanePositions, link));
220 }
221 else
222 {
223 Double weight = linkWeights.get(link);
224 Throw.whenNull(weight, "Using link weights for GTU generation, but no weight for link %s is defined.", link);
225 linkPositions.add(new GeneratorLinkPosition(lanePositions, link, weight));
226 }
227 }
228
229
230 return new GeneratorPositions(new GeneratorZonePosition(linkPositions), biases, stream);
231
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245 public GeneratorLanePosition draw(final GTUType gtuType, final Map<CrossSectionLink, Map<Integer, Integer>> unplaced,
246 final Speed desiredSpeed, final Route route)
247 {
248 return this.position.draw(gtuType, this.stream, this.biases, unplaced, desiredSpeed, route);
249 }
250
251
252 @Override
253 public DirectedPoint getLocation() throws RemoteException
254 {
255 return this.location;
256 }
257
258
259 @Override
260 public Bounds getBounds() throws RemoteException
261 {
262 return this.bounds;
263 }
264
265
266
267
268
269 public Set<GeneratorLanePosition> getAllPositions()
270 {
271 return this.allPositions;
272 }
273
274
275
276
277
278
279 public Speed speedLimit(final GTUType gtuType)
280 {
281 Speed speedLimit = null;
282 for (GeneratorLanePosition pos : this.allPositions)
283 {
284 for (DirectedLanePosition lane : pos.getPosition())
285 {
286 try
287 {
288 Speed limit = lane.getLane().getSpeedLimit(gtuType);
289 if (speedLimit == null || limit.lt(speedLimit))
290 {
291 speedLimit = limit;
292 }
293 }
294 catch (NetworkException exception)
295 {
296
297 }
298 }
299 }
300 Throw.when(speedLimit == null, IllegalStateException.class, "No speed limit could be determined for GTUType %s.",
301 gtuType);
302 return speedLimit;
303 }
304
305
306
307
308
309
310
311
312
313
314
315
316
317 public static final class GeneratorLanePosition
318 {
319
320
321 private final int laneNumber;
322
323
324 private final Set<DirectedLanePosition> position;
325
326
327 private final CrossSectionLink link;
328
329
330
331
332
333
334
335 GeneratorLanePosition(final int laneNumber, final Set<DirectedLanePosition> position, final CrossSectionLink link)
336 {
337 this.laneNumber = laneNumber;
338 this.position = position;
339 this.link = link;
340 }
341
342
343
344
345
346 int getLaneNumber()
347 {
348 return this.laneNumber;
349 }
350
351
352
353
354
355
356 boolean allows(final GTUType gtuType)
357 {
358 for (DirectedLanePosition pos : this.position)
359 {
360 if (pos.getLane().getLaneType().isCompatible(gtuType, pos.getGtuDirection()))
361 {
362 return true;
363 }
364 }
365 return false;
366 }
367
368
369
370
371
372 Set<DirectedLanePosition> getPosition()
373 {
374 return this.position;
375 }
376
377
378
379
380
381 CrossSectionLink getLink()
382 {
383 return this.link;
384 }
385
386
387
388
389
390 GTUDirectionality getDirection()
391 {
392 return this.position.iterator().next().getGtuDirection();
393 }
394
395
396 @Override
397 public String toString()
398 {
399 return "GeneratorLanePosition [laneNumber=" + this.laneNumber + ", position=" + this.position + ", link="
400 + this.link + "]";
401 }
402
403 }
404
405
406
407
408
409
410
411
412
413
414
415
416
417 private static final class GeneratorLinkPosition
418 {
419
420
421 private final List<GeneratorLanePosition> positions;
422
423
424 private final CrossSectionLink link;
425
426
427 private final double weight;
428
429
430
431
432
433
434 GeneratorLinkPosition(final List<GeneratorLanePosition> positions, final CrossSectionLink link)
435 {
436 this.positions = positions;
437 this.link = link;
438 this.weight = -1;
439 }
440
441
442
443
444
445
446
447 GeneratorLinkPosition(final List<GeneratorLanePosition> positions, final CrossSectionLink link, final double weight)
448 {
449 this.positions = positions;
450 this.link = link;
451 this.weight = weight;
452 }
453
454
455
456
457
458 CrossSectionLink getLink()
459 {
460 return this.link;
461 }
462
463
464
465
466
467
468 double getWeight(final GTUType gtuType)
469 {
470 if (this.weight < 0.0)
471 {
472 return getNumberOfLanes(gtuType);
473 }
474 return this.weight;
475 }
476
477
478
479
480
481
482 int getNumberOfLanes(final GTUType gtuType)
483 {
484 int numberOfLanes = 0;
485 for (GeneratorLanePosition lanePosition : this.positions)
486 {
487 if (lanePosition.allows(gtuType))
488 {
489 numberOfLanes++;
490 }
491 }
492 return numberOfLanes;
493 }
494
495
496
497
498
499
500
501
502
503
504
505
506 GeneratorLanePosition draw(final GTUType gtuType, final StreamInterface stream, final LaneBiases biases,
507 final Map<Integer, Integer> unplaced, final Speed desiredSpeed)
508 {
509 Map<GeneratorLanePosition, Double> map = new LinkedHashMap<>();
510
511
512 for (int i = 0; i < this.positions.size(); i++)
513 {
514 GeneratorLanePosition lanePosition = this.positions.get(i);
515 if (lanePosition.allows(gtuType))
516 {
517 GTUType type = gtuType;
518 boolean found = false;
519 while (biases != null && !found && type != null)
520 {
521 if (biases.contains(type))
522 {
523 found = true;
524 int laneNum = lanePosition.getLaneNumber();
525 int unplacedTemplates = unplaced == null ? 0 : unplaced.getOrDefault(laneNum, 0);
526 double w = biases.getBias(type).calculateWeight(laneNum, getNumberOfLanes(gtuType),
527 unplacedTemplates, desiredSpeed);
528 map.put(lanePosition, w);
529
530 }
531 type = type.getParent();
532 }
533 if (!found)
534 {
535 map.put(lanePosition, 1.0);
536
537 }
538
539 }
540 }
541 return Draw.drawWeighted(map, stream);
542
543
544
545
546
547
548
549
550
551 }
552
553
554
555
556
557 GTUDirectionality getDirection()
558 {
559 return this.positions.get(0).getDirection();
560 }
561
562
563 @Override
564 public String toString()
565 {
566 return "GeneratorLinkPosition [positions=" + this.positions + "]";
567 }
568
569 }
570
571
572
573
574
575
576
577
578
579
580
581
582
583 private static final class GeneratorZonePosition
584 {
585
586
587 private final List<GeneratorLinkPosition> positions;
588
589
590
591
592
593 GeneratorZonePosition(final List<GeneratorLinkPosition> positions)
594 {
595 this.positions = positions;
596 }
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612 GeneratorLanePosition draw(final GTUType gtuType, final StreamInterface stream, final LaneBiases biases,
613 final Map<CrossSectionLink, Map<Integer, Integer>> unplaced, final Speed desiredSpeed, final Route route)
614 {
615 Map<GeneratorLinkPosition, Double> map = new LinkedHashMap<>();
616
617
618 for (int i = 0; i < this.positions.size(); i++)
619 {
620 Link link = this.positions.get(i).getLink();
621 GTUDirectionality direction = this.positions.get(i).getDirection();
622 if (route != null)
623 {
624 int from = route.indexOf(direction.isPlus() ? link.getStartNode() : link.getEndNode());
625 int to = route.indexOf(direction.isPlus() ? link.getEndNode() : link.getStartNode());
626 if (from > -1 && to > -1 && to - from == 1)
627 {
628 map.put(this.positions.get(i), this.positions.get(i).getWeight(gtuType));
629
630 }
631 }
632 else
633 {
634 map.put(this.positions.get(i), this.positions.get(i).getWeight(gtuType));
635
636 }
637
638 }
639
640 GeneratorLinkPosition linkPosition = Draw.drawWeighted(map, stream);
641 return linkPosition.draw(gtuType, stream, biases, unplaced.get(linkPosition.getLink()), desiredSpeed);
642
643
644
645
646
647
648
649
650
651
652
653
654 }
655
656
657 @Override
658 public String toString()
659 {
660 return "GeneratorZonePosition [positions=" + this.positions + "]";
661 }
662
663 }
664
665
666
667
668
669
670
671
672
673
674
675
676
677 public static final class LaneBiases
678 {
679
680
681 private final Map<GTUType, LaneBias> biases = new HashMap<>();
682
683
684
685
686
687
688
689 public LaneBiases addBias(final GTUType gtuType, final LaneBias bias)
690 {
691 Throw.whenNull(gtuType, "GTU type may not be null.");
692 Throw.whenNull(bias, "Bias may not be null.");
693 this.biases.put(gtuType, bias);
694 return this;
695 }
696
697
698
699
700
701
702 public boolean contains(final GTUType gtuType)
703 {
704 return this.biases.containsKey(gtuType);
705 }
706
707
708
709
710
711
712 public LaneBias getBias(final GTUType gtuType)
713 {
714 return this.biases.getOrDefault(gtuType, LaneBias.NONE);
715 }
716
717
718 @Override
719 public String toString()
720 {
721 return "LaneBiases [" + this.biases + "]";
722 }
723
724 }
725
726
727
728
729
730
731
732
733
734
735
736
737
738 public static final class LaneBias
739 {
740
741
742 public static final LaneBias NONE = new LaneBias(new ByValue(0.0), 0.0, Integer.MAX_VALUE);
743
744
745 public static final LaneBias WEAK_LEFT = new LaneBias(new ByValue(1.0), 1.0, Integer.MAX_VALUE);
746
747
748 public static final LaneBias LEFT = new LaneBias(new ByValue(1.0), 2.0, Integer.MAX_VALUE);
749
750
751 public static final LaneBias STRONG_LEFT = new LaneBias(new ByValue(1.0), 5.0, Integer.MAX_VALUE);
752
753
754 public static final LaneBias WEAK_MIDDLE = new LaneBias(new ByValue(0.5), 1.0, Integer.MAX_VALUE);
755
756
757 public static final LaneBias MIDDLE = new LaneBias(new ByValue(0.5), 2.0, Integer.MAX_VALUE);
758
759
760 public static final LaneBias STRONG_MIDDLE = new LaneBias(new ByValue(0.5), 5.0, Integer.MAX_VALUE);
761
762
763 public static final LaneBias WEAK_RIGHT = new LaneBias(new ByValue(0.0), 1.0, Integer.MAX_VALUE);
764
765
766 public static final LaneBias RIGHT = new LaneBias(new ByValue(0.0), 2.0, Integer.MAX_VALUE);
767
768
769 public static final LaneBias STRONG_RIGHT = new LaneBias(new ByValue(0.0), 5.0, Integer.MAX_VALUE);
770
771
772 public static final LaneBias TRUCK_RIGHT = new LaneBias(new ByValue(0.0), 5.0, 2);
773
774
775
776
777
778
779
780 public static LaneBias bySpeed(final Speed leftSpeed, final Speed rightSpeed)
781 {
782 return new LaneBias(new BySpeed(leftSpeed, rightSpeed), 2.0, Integer.MAX_VALUE);
783 }
784
785
786
787
788
789
790
791 public static LaneBias bySpeed(final double leftSpeedKm, final double rightSpeedKm)
792 {
793 return bySpeed(new Speed(leftSpeedKm, SpeedUnit.KM_PER_HOUR), new Speed(rightSpeedKm, SpeedUnit.KM_PER_HOUR));
794 }
795
796
797 private final RoadPosition roadPosition;
798
799
800 private final double bias;
801
802
803 private final double stickyLanes;
804
805
806
807
808
809
810
811 public LaneBias(final RoadPosition roadPosition, final double bias, final double stickyLanes)
812 {
813 Throw.when(bias < 0.0, IllegalArgumentException.class, "Bias should be positive or 0.");
814 Throw.when(stickyLanes < 1.0, IllegalArgumentException.class, "Sticky lanes should be 1.0 or larger.");
815 this.roadPosition = roadPosition;
816 this.bias = bias;
817 this.stickyLanes = stickyLanes;
818 }
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854 public double calculateWeight(final int laneNumFromRight, final int numberOfLanes, final int numberOfUnplacedGTUs,
855 final Speed desiredSpeed)
856 {
857 double d = Math.abs((1.0 + this.roadPosition.getValue(desiredSpeed) * (numberOfLanes - 1.0)) - laneNumFromRight);
858 if (d >= this.stickyLanes)
859 {
860 return 0.0;
861 }
862 return 1.0 / (Math.pow(d + 1.0, this.bias) * (numberOfUnplacedGTUs + 1.0));
863 }
864
865
866 @Override
867 public int hashCode()
868 {
869 final int prime = 31;
870 int result = 1;
871 long temp;
872 temp = Double.doubleToLongBits(this.bias);
873 result = prime * result + (int) (temp ^ (temp >>> 32));
874 result = prime * result + ((this.roadPosition == null) ? 0 : this.roadPosition.hashCode());
875 temp = Double.doubleToLongBits(this.stickyLanes);
876 result = prime * result + (int) (temp ^ (temp >>> 32));
877 return result;
878 }
879
880
881 @Override
882 public boolean equals(final Object obj)
883 {
884 if (this == obj)
885 {
886 return true;
887 }
888 if (obj == null)
889 {
890 return false;
891 }
892 if (getClass() != obj.getClass())
893 {
894 return false;
895 }
896 LaneBias other = (LaneBias) obj;
897 if (Double.doubleToLongBits(this.bias) != Double.doubleToLongBits(other.bias))
898 {
899 return false;
900 }
901 if (this.roadPosition == null)
902 {
903 if (other.roadPosition != null)
904 {
905 return false;
906 }
907 }
908 else if (!this.roadPosition.equals(other.roadPosition))
909 {
910 return false;
911 }
912 if (Double.doubleToLongBits(this.stickyLanes) != Double.doubleToLongBits(other.stickyLanes))
913 {
914 return false;
915 }
916 return true;
917 }
918
919
920 @Override
921 public String toString()
922 {
923 return "Bias [roadPosition=" + this.roadPosition + ", bias=" + this.bias + ", stickyLanes=" + this.stickyLanes
924 + "]";
925 }
926
927 }
928
929
930
931
932
933
934
935
936
937
938
939
940
941 interface RoadPosition
942 {
943
944
945
946
947
948
949 double getValue(Speed desiredSpeed);
950
951
952
953
954
955
956
957
958
959
960
961
962
963 class ByValue implements RoadPosition
964 {
965
966
967 private double value;
968
969
970
971
972
973 ByValue(final double value)
974 {
975 Throw.when(value < 0.0 || value > 1.0, IllegalArgumentException.class,
976 "Road position value should be in the range [0...1].");
977 this.value = value;
978 }
979
980
981 @Override
982 public double getValue(final Speed desiredSpeed)
983 {
984 return this.value;
985 }
986
987
988 @Override
989 public int hashCode()
990 {
991 final int prime = 31;
992 int result = 1;
993 long temp;
994 temp = Double.doubleToLongBits(this.value);
995 result = prime * result + (int) (temp ^ (temp >>> 32));
996 return result;
997 }
998
999
1000 @Override
1001 public boolean equals(final Object obj)
1002 {
1003 if (this == obj)
1004 {
1005 return true;
1006 }
1007 if (obj == null)
1008 {
1009 return false;
1010 }
1011 if (getClass() != obj.getClass())
1012 {
1013 return false;
1014 }
1015 ByValue other = (ByValue) obj;
1016 if (Double.doubleToLongBits(this.value) != Double.doubleToLongBits(other.value))
1017 {
1018 return false;
1019 }
1020 return true;
1021 }
1022
1023 }
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037 class BySpeed implements RoadPosition
1038 {
1039
1040
1041 private Speed leftSpeed;
1042
1043
1044 private Speed rightSpeed;
1045
1046
1047
1048
1049
1050
1051 BySpeed(final Speed leftSpeed, final Speed rightSpeed)
1052 {
1053 Throw.when(leftSpeed.eq(rightSpeed), IllegalArgumentException.class,
1054 "Left speed and right speed may not be equal. Use LaneBias.NONE.");
1055 this.leftSpeed = leftSpeed;
1056 this.rightSpeed = rightSpeed;
1057 }
1058
1059
1060 @Override
1061 public double getValue(final Speed desiredSpeed)
1062 {
1063 Throw.whenNull(desiredSpeed,
1064 "Peeked desired speed from a strategical planner factory is null, while a lane bias depends on desired speed.");
1065 double value = (desiredSpeed.si - this.rightSpeed.si) / (this.leftSpeed.si - this.rightSpeed.si);
1066 return value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);
1067 }
1068
1069
1070 @Override
1071 public int hashCode()
1072 {
1073 final int prime = 31;
1074 int result = 1;
1075 result = prime * result + ((this.leftSpeed == null) ? 0 : this.leftSpeed.hashCode());
1076 result = prime * result + ((this.rightSpeed == null) ? 0 : this.rightSpeed.hashCode());
1077 return result;
1078 }
1079
1080
1081 @Override
1082 public boolean equals(final Object obj)
1083 {
1084 if (this == obj)
1085 {
1086 return true;
1087 }
1088 if (obj == null)
1089 {
1090 return false;
1091 }
1092 if (getClass() != obj.getClass())
1093 {
1094 return false;
1095 }
1096 BySpeed other = (BySpeed) obj;
1097 if (this.leftSpeed == null)
1098 {
1099 if (other.leftSpeed != null)
1100 {
1101 return false;
1102 }
1103 }
1104 else if (!this.leftSpeed.equals(other.leftSpeed))
1105 {
1106 return false;
1107 }
1108 if (this.rightSpeed == null)
1109 {
1110 if (other.rightSpeed != null)
1111 {
1112 return false;
1113 }
1114 }
1115 else if (!this.rightSpeed.equals(other.rightSpeed))
1116 {
1117 return false;
1118 }
1119 return true;
1120 }
1121
1122 }
1123
1124 }
1125
1126 }