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