1 package org.opentrafficsim.road.network.lane.conflict;
2
3 import java.util.LinkedHashMap;
4 import java.util.Map;
5
6 import org.djunits.value.vdouble.scalar.Length;
7 import org.djutils.exceptions.Throw;
8 import org.opentrafficsim.core.geometry.DirectedPoint;
9 import org.opentrafficsim.core.geometry.OtsGeometryException;
10 import org.opentrafficsim.road.network.lane.CrossSectionLink.Priority;
11 import org.opentrafficsim.road.network.lane.Lane;
12
13 /**
14 * Default determination of priority based on link priority, or right-hand traffic. Note that this class is stateful as the
15 * priorities are cached. So each conflict pair should receive a separate {@code DefaultConflictRule}. This rule is only for use
16 * on merge and crossing conflicts. For split conflicts there is a separate rule {@code SplitConflictRule}.
17 * <p>
18 * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
20 * </p>
21 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
22 * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
23 * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
24 */
25 public class DefaultConflictRule implements ConflictRule
26 {
27
28 /** Priority per conflict. */
29 private Map<String, ConflictPriority> map = null;
30
31 // Throw.whenNull(priority1, "Conflict rule may not be null.");
32 // Throw.whenNull(priority2, "Conflict rule may not be null.");
33 // if (priority1.equals(ConflictPriority.SPLIT) || priority2.equals(ConflictPriority.SPLIT))
34 // {
35 // // Split with split (on split)
36 // Throw.when(!priority1.equals(ConflictPriority.SPLIT) || !priority2.equals(ConflictPriority.SPLIT),
37 // NetworkException.class, "Both conflict rules should be split for conflict type split.");
38 // }
39 // else
40 // {
41 // // Priority with give-way/stop
42 // boolean check1 = priority1.equals(ConflictPriority.PRIORITY) && !priority2.equals(ConflictPriority.GIVE_WAY)
43 // && !priority2.equals(ConflictPriority.STOP);
44 // boolean check2 = priority2.equals(ConflictPriority.PRIORITY) && !priority1.equals(ConflictPriority.GIVE_WAY)
45 // && !priority1.equals(ConflictPriority.STOP);
46 // boolean check3 =
47 // priority1.equals(ConflictPriority.GIVE_WAY) && !priority2.equals(ConflictPriority.PRIORITY);
48 // boolean check4 =
49 // priority2.equals(ConflictPriority.GIVE_WAY) && !priority1.equals(ConflictPriority.PRIORITY);
50 // boolean check5 = priority1.equals(ConflictPriority.STOP) && !priority2.equals(ConflictPriority.PRIORITY);
51 // boolean check6 = priority2.equals(ConflictPriority.STOP) && !priority1.equals(ConflictPriority.PRIORITY);
52 // Throw.when(check1 || check2 || check3 || check4 || check5 || check6, NetworkException.class,
53 // "Conflict rules need to be a combination of 'PRIORITY' and 'GIVE_WAY' or 'STOP', "
54 // + "if any of these types is used.");
55 // // All-stop with all-stop
56 // boolean check7 =
57 // priority1.equals(ConflictPriority.ALL_STOP) && !priority2.equals(ConflictPriority.ALL_STOP);
58 // boolean check8 =
59 // priority2.equals(ConflictPriority.ALL_STOP) && !priority1.equals(ConflictPriority.ALL_STOP);
60 // Throw.when(check7 || check8, NetworkException.class,
61 // "Conflict rule 'ALL_STOP' can only be combined with a conflict rule 'ALL_STOP'.");
62 // // No split
63 // Throw.when(priority1.equals(ConflictPriority.SPLIT) || priority2.equals(ConflictPriority.SPLIT),
64 // NetworkException.class, "Conflict rule 'SPLIT' may only be used on conflicts of type SPLIT.");
65 // }
66
67 /** {@inheritDoc} */
68 @Override
69 public ConflictPriority determinePriority(final Conflict conflict)
70 {
71 if (this.map == null)
72 {
73 ConflictPriority[] conflictPriorities = getConflictRules(conflict.getLane(), conflict.getLongitudinalPosition(),
74 conflict.getOtherConflict().getLane(), conflict.getOtherConflict().getLongitudinalPosition(),
75 conflict.getConflictType());
76 this.map = new LinkedHashMap<>();
77 this.map.put(conflict.getId(), conflictPriorities[0]);
78 this.map.put(conflict.getOtherConflict().getId(), conflictPriorities[1]);
79 }
80 ConflictPriority out = this.map.get(conflict.getId());
81 Throw.when(out == null, IllegalArgumentException.class,
82 "Conflict %s is not related to a conflict that was used before in the same conflict rule.", conflict);
83 return out;
84 }
85
86 /**
87 * Determine conflict rules.
88 * @param lane1 Lane; lane 1
89 * @param longitudinalPosition1 Length; position 1
90 * @param lane2 Lane; lane 2
91 * @param longitudinalPosition2 Length; position 2
92 * @param conflictType ConflictType; conflict type
93 * @return conflict rule 1 and 2
94 */
95 private static ConflictPriority[] getConflictRules(final Lane lane1, final Length longitudinalPosition1, final Lane lane2,
96 final Length longitudinalPosition2, final ConflictType conflictType)
97 {
98 Throw.when(conflictType.equals(ConflictType.SPLIT), UnsupportedOperationException.class,
99 "DefaultConflictRule is not for use on a split conflict. Use SplitConflictRule instead.");
100 ConflictPriority[] conflictRules = new ConflictPriority[2];
101 Priority priority1 = lane1.getParentLink().getPriority();
102 Priority priority2 = lane2.getParentLink().getPriority();
103 if (priority1.isAllStop() && priority2.isAllStop())
104 {
105 conflictRules[0] = ConflictPriority.ALL_STOP;
106 conflictRules[1] = ConflictPriority.ALL_STOP;
107 }
108 else if (priority1.equals(priority2) || (priority1.isYield() && priority2.isStop())
109 || (priority2.isYield() && priority1.isStop()))
110 {
111 Throw.when(priority1.isTurnOnRed() || priority1.isBusStop(), IllegalArgumentException.class,
112 "Both priorities are either 'turn on red' or 'bus stop', which is not allowed. "
113 + "Use BusStopConflictRule for bus stops.");
114 // Based on right- or left-hand traffic
115 DirectedPoint p1;
116 DirectedPoint p2;
117 try
118 {
119 p1 = lane1.getCenterLine().getLocation(longitudinalPosition1);
120 p2 = lane2.getCenterLine().getLocation(longitudinalPosition2);
121 }
122 catch (OtsGeometryException exception)
123 {
124 throw new RuntimeException("Conflict position is not on its lane.", exception);
125 }
126 double diff = p2.getRotZ() - p1.getRotZ();
127 while (diff > Math.PI)
128 {
129 diff -= 2 * Math.PI;
130 }
131 while (diff < -Math.PI)
132 {
133 diff += 2 * Math.PI;
134 }
135 if (diff > 0.0)
136 {
137 // 2 comes from the right
138 conflictRules[0] = priority1.isStop() ? ConflictPriority.STOP : ConflictPriority.YIELD;
139 conflictRules[1] = ConflictPriority.PRIORITY;
140 }
141 else
142 {
143 // 1 comes from the right
144 conflictRules[0] = ConflictPriority.PRIORITY;
145 conflictRules[1] = priority2.isStop() ? ConflictPriority.STOP : ConflictPriority.YIELD;
146 }
147 }
148 else if ((priority1.isPriority() || priority1.isNone()) // note, both NONE already captured
149 && (priority2.isNone() || priority2.isYield() || priority2.isStop()))
150 {
151 conflictRules[0] = ConflictPriority.PRIORITY;
152 conflictRules[1] = priority2.isStop() ? ConflictPriority.STOP : ConflictPriority.YIELD;
153 }
154 else if ((priority2.isPriority() || priority2.isNone())
155 && (priority1.isNone() || priority1.isYield() || priority1.isStop()))
156 {
157 conflictRules[0] = priority1.isStop() ? ConflictPriority.STOP : ConflictPriority.YIELD;
158 conflictRules[1] = ConflictPriority.PRIORITY;
159 }
160 else if (priority1.isTurnOnRed() && (priority2.isYield() || priority2.isStop()))
161 {
162 conflictRules[0] = ConflictPriority.TURN_ON_RED;
163 conflictRules[1] = ConflictPriority.PRIORITY;
164 }
165 else if (priority2.isTurnOnRed() && (priority1.isYield() || priority1.isStop()))
166 {
167 conflictRules[0] = ConflictPriority.PRIORITY;
168 conflictRules[1] = ConflictPriority.TURN_ON_RED;
169 }
170 else
171 {
172 throw new IllegalArgumentException(
173 "Could not sort out conflict priority from link priorities " + priority1 + " and " + priority2);
174 }
175 return conflictRules;
176 }
177
178 /** {@inheritDoc} */
179 @Override
180 public final String toString()
181 {
182 return "DefaultConflictRule";
183 }
184
185 }