Fix issue with turn type

This commit is contained in:
Victor Shcherb 2014-11-23 19:45:31 +01:00
parent c6d6897a88
commit bcc8ab6c7a
2 changed files with 165 additions and 184 deletions

View file

@ -1,16 +1,16 @@
package net.osmand.router; package net.osmand.router;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.set.hash.TIntHashSet;
import java.io.IOException; import java.io.IOException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.commons.logging.Log;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.binary.BinaryMapIndexReader; import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteTypeRule; import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteTypeRule;
@ -21,6 +21,8 @@ import net.osmand.router.BinaryRoutePlanner.RouteSegment;
import net.osmand.router.RoutePlannerFrontEnd.RouteCalculationMode; import net.osmand.router.RoutePlannerFrontEnd.RouteCalculationMode;
import net.osmand.util.MapUtils; import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
public class RouteResultPreparation { public class RouteResultPreparation {
public static boolean PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST = false; public static boolean PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST = false;
@ -440,7 +442,10 @@ public class RouteResultPreparation {
} else { } else {
t = TurnType.valueOf(TurnType.TU, leftSide); t = TurnType.valueOf(TurnType.TU, leftSide);
} }
assignLanesInfo(prev, t, leftSide); int[] lanes = getTurnLanesInfo(prev, t.getValue(), leftSide, true);
if(lanes != null) {
t.setLanes(lanes);
}
} else if (mpi < -TURN_DEGREE_MIN) { } else if (mpi < -TURN_DEGREE_MIN) {
if (mpi > -60) { if (mpi > -60) {
t = TurnType.valueOf(TurnType.TSLR, leftSide); t = TurnType.valueOf(TurnType.TSLR, leftSide);
@ -451,7 +456,10 @@ public class RouteResultPreparation {
} else { } else {
t = TurnType.valueOf(TurnType.TU, leftSide); t = TurnType.valueOf(TurnType.TU, leftSide);
} }
assignLanesInfo(prev, t, leftSide); int[] lanes = getTurnLanesInfo(prev, t.getValue(), leftSide, false);
if(lanes != null) {
t.setLanes(lanes);
}
} else { } else {
t = attachKeepLeftInfoAndLanes(leftSide, prev, rr, t); t = attachKeepLeftInfoAndLanes(leftSide, prev, rr, t);
} }
@ -462,62 +470,52 @@ public class RouteResultPreparation {
return t; return t;
} }
private void assignLanesInfo(RouteSegmentResult prevSegm, TurnType t, boolean leftSide) { private int[] getTurnLanesInfo(RouteSegmentResult prevSegm, int mainTurnType, boolean leftSide,
int lanes = prevSegm.getObject().getLanes(); boolean leftTurn) {
if (prevSegm.getObject().getOneway() == 0) {
lanes = countLanes(prevSegm, lanes);
}
if (lanes <= 0) {
return;
}
String turnLanes = getTurnLanesString(prevSegm); String turnLanes = getTurnLanesString(prevSegm);
if (turnLanes == null) { if (turnLanes == null) {
return; return null;
} }
String[] splitLaneOptions = turnLanes.split("\\|", -1); String[] splitLaneOptions = turnLanes.split("\\|", -1);
if (splitLaneOptions.length != lanes) { if (splitLaneOptions.length != countLanesMinOne(prevSegm)) {
// Error in data or missing data // Error in data or missing data
return; return null;
} }
int[] lanesArray = calculateRawTurnLanes(splitLaneOptions, mainTurnType);
int[] lanesArray = new int[lanes];
t.setLanes(lanesArray);
assignTurns(splitLaneOptions, t);
// In some cases (at least in the US), the rightmost lane might not have a right turn indicated as per turn:lanes, but is allowed and being used here. This section adds in that indicator. The same applies for where leftSide is true.
if (leftSide) {
if (t.getValue() == TurnType.TL
&& TurnType.getPrimaryTurn(lanesArray[0]) != TurnType.TL
&& TurnType.getPrimaryTurn(lanesArray[0]) != TurnType.TSLL
&& TurnType.getPrimaryTurn(lanesArray[0]) != TurnType.TSHL) {
if (TurnType.getPrimaryTurn(lanesArray[0]) != 0) {
// This was just to make sure that there's no bad data.
t.setSecondaryTurn(0, TurnType.getPrimaryTurn(lanesArray[0]));
t.setPrimaryTurn(0, TurnType.TL);
}
}
} else {
int lastIndex = lanesArray.length - 1;
if (t.getValue() == TurnType.TR
&& TurnType.getPrimaryTurn(lanesArray[lastIndex]) != TurnType.TR
&& TurnType.getPrimaryTurn(lanesArray[lastIndex]) != TurnType.TSLR
&& TurnType.getPrimaryTurn(lanesArray[lastIndex]) != TurnType.TSHR) {
if (TurnType.getPrimaryTurn(lanesArray[lastIndex]) != 0) {
// This was just to make sure that there's no bad data.
t.setSecondaryTurn(lastIndex, TurnType.getPrimaryTurn(lanesArray[lastIndex]));
t.setPrimaryTurn(lastIndex, TurnType.TR);
}
}
}
// Manually set the allowed lanes. // Manually set the allowed lanes.
boolean isSet = setAllowedLanes(mainTurnType, lanesArray);
if(!isSet && lanesArray.length > 0) {
// In some cases (at least in the US), the rightmost lane might not have a right turn indicated as per turn:lanes,
// but is allowed and being used here. This section adds in that indicator. The same applies for where leftSide is true.
int ind = leftTurn? 0 : lanesArray.length - 1;
final int tt = TurnType.getPrimaryTurn(lanesArray[ind]);
if (leftTurn) {
if (!TurnType.isLeftTurn(tt)) {
// This was just to make sure that there's no bad data.
TurnType.setSecondaryTurn(lanesArray, ind, tt);
TurnType.setPrimaryTurn(lanesArray, ind, TurnType.TL);
}
} else {
if (!TurnType.isRightTurn(tt)) {
// This was just to make sure that there's no bad data.
TurnType.setSecondaryTurn(lanesArray, ind, tt);
TurnType.setPrimaryTurn(lanesArray, ind, TurnType.TR);
}
}
setAllowedLanes(lanesArray[ind], lanesArray);
}
return lanesArray;
}
protected boolean setAllowedLanes(int mainTurnType, int[] lanesArray) {
boolean turnSet = false;
for (int i = 0; i < lanesArray.length; i++) { for (int i = 0; i < lanesArray.length; i++) {
if (TurnType.getPrimaryTurn(lanesArray[i]) == t.getValue()) { if (TurnType.getPrimaryTurn(lanesArray[i]) == mainTurnType) {
lanesArray[i] |= 1; lanesArray[i] |= 1;
turnSet = true;
} }
} }
return turnSet;
} }
private TurnType processRoundaboutTurn(List<RouteSegmentResult> result, int i, boolean leftSide, RouteSegmentResult prev, private TurnType processRoundaboutTurn(List<RouteSegmentResult> result, int i, boolean leftSide, RouteSegmentResult prev,
@ -554,7 +552,6 @@ public class RouteResultPreparation {
private TurnType attachKeepLeftInfoAndLanes(boolean leftSide, RouteSegmentResult prevSegm, RouteSegmentResult currentSegm, TurnType t) { private TurnType attachKeepLeftInfoAndLanes(boolean leftSide, RouteSegmentResult prevSegm, RouteSegmentResult currentSegm, TurnType t) {
// keep left/right // keep left/right
int[] lanes = null;
boolean kl = false; boolean kl = false;
boolean kr = false; boolean kr = false;
List<RouteSegmentResult> attachedRoutes = currentSegm.getAttachedRoutes(currentSegm.getStartPointIndex()); List<RouteSegmentResult> attachedRoutes = currentSegm.getAttachedRoutes(currentSegm.getStartPointIndex());
@ -574,27 +571,11 @@ public class RouteResultPreparation {
if (rsSpeakPriority != MAX_SPEAK_PRIORITY || speakPriority == MAX_SPEAK_PRIORITY) { if (rsSpeakPriority != MAX_SPEAK_PRIORITY || speakPriority == MAX_SPEAK_PRIORITY) {
if ((ex < TURN_DEGREE_MIN || mpi < TURN_DEGREE_MIN) && ex >= 0) { if ((ex < TURN_DEGREE_MIN || mpi < TURN_DEGREE_MIN) && ex >= 0) {
kl = true; kl = true;
int lns = attached.getObject().getLanes(); right += countLanesMinOne(attached);
if(attached.getObject().getOneway() == 0) {
lns = countLanes(attached, lns);
}
if (lns <= 0) {
right += 1;
} else {
right += lns;
}
speak = speak || rsSpeakPriority <= speakPriority; speak = speak || rsSpeakPriority <= speakPriority;
} else if ((ex > -TURN_DEGREE_MIN || mpi < TURN_DEGREE_MIN) && ex <= 0) { } else if ((ex > -TURN_DEGREE_MIN || mpi < TURN_DEGREE_MIN) && ex <= 0) {
kr = true; kr = true;
int lns = attached.getObject().getLanes(); left += countLanesMinOne(attached);
if(attached.getObject().getOneway() == 0) {
lns = countLanes(attached, lns);
}
if (lns <= 0) {
left += 1;
} else {
left += lns;
}
speak = speak || rsSpeakPriority <= speakPriority; speak = speak || rsSpeakPriority <= speakPriority;
} }
} }
@ -605,29 +586,20 @@ public class RouteResultPreparation {
} else if(kl && right == 0) { } else if(kl && right == 0) {
right = 1; right = 1;
} }
int current = currentSegm.getObject().getLanes(); int current = countLanesMinOne(currentSegm);
// attachedRoutes covers all allowed outbound routes at that point except currentSegm. int[] lanes = new int[current + left + right];
if (currentSegm.getObject().getOneway() == 0) { ls = current + left + right;
current = countLanes(currentSegm, current); for (int it = 0; it < ls; it++) {
} if (it < left || it >= left + current) {
if (current <= 0) { lanes[it] = 0;
current = 1; } else {
} lanes[it] = 1;
// if(ls >= 0 /*&& current + left + right >= ls*/){
lanes = new int[current + left + right];
ls = current + left + right;
for(int it=0; it< ls; it++) {
if(it < left || it >= left + current) {
lanes[it] = 0;
} else {
lanes[it] = 1;
}
} }
// sometimes links are }
if ((current <= left + right) && (left > 1 || right > 1)) { // sometimes links are
speak = true; if ((current <= left + right) && (left > 1 || right > 1)) {
} speak = true;
// } }
double devation = Math.abs(MapUtils.degreesDiff(prevSegm.getBearingEnd(), currentSegm.getBearingBegin())); double devation = Math.abs(MapUtils.degreesDiff(prevSegm.getBearingEnd(), currentSegm.getBearingBegin()));
boolean makeSlightTurn = devation > 5 && (!isMotorway(prevSegm) || !isMotorway(currentSegm)); boolean makeSlightTurn = devation > 5 && (!isMotorway(prevSegm) || !isMotorway(currentSegm));
@ -640,14 +612,42 @@ public class RouteResultPreparation {
t.setSkipToSpeak(!speak); t.setSkipToSpeak(!speak);
} }
if (t != null && lanes != null) { if (t != null && lanes != null) {
t.setLanes(lanes); int[] calcLanes = attachTurnLanesData(prevSegm, lanes);
if(calcLanes != lanes) {
int tp = inferTurnFromLanes(calcLanes);
if (tp != 0 && tp != t.getValue()) {
TurnType derivedTurnType = TurnType.valueOf(tp, leftSide);
derivedTurnType.setLanes(calcLanes);
derivedTurnType.setSkipToSpeak(t.isSkipToSpeak());
// Because only the primary turn is displayed, if the turn to be taken is currently set as the
// secondary turn, then that needs to be switched around.
for (int i = 0; i < calcLanes.length; i++) {
if (TurnType.getSecondaryTurn(calcLanes[i]) == tp) {
derivedTurnType.setSecondaryTurn(i, TurnType.getPrimaryTurn(calcLanes[i]));
derivedTurnType.setPrimaryTurn(i, tp);
}
}
}
}
t.setLanes(calcLanes);
t = attachTurnLanesData(leftSide, prevSegm, t);
} }
return t; return t;
} }
protected int countLanes(RouteSegmentResult attached, int lns) {
protected int countLanesMinOne(RouteSegmentResult attached) {
final boolean oneway = attached.getObject().getOneway() != 0;
int lns = attached.getObject().getLanes();
if(lns == 0) {
String tls = getTurnLanesString(attached);
if(tls != null) {
lns = countOccurrences(tls, '|');
}
}
if (oneway) {
return Math.max(1, lns);
}
try { try {
if (attached.isForwardDirection() && attached.getObject().getValue("lanes:forward") != null) { if (attached.isForwardDirection() && attached.getObject().getValue("lanes:forward") != null) {
return Integer.parseInt(attached.getObject().getValue("lanes:forward")); return Integer.parseInt(attached.getObject().getValue("lanes:forward"));
@ -657,7 +657,7 @@ public class RouteResultPreparation {
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
e.printStackTrace(); e.printStackTrace();
} }
return (lns + 1) / 2; return Math.max(1, (lns + 1) / 2);
} }
protected String getTurnLanesString(RouteSegmentResult segment) { protected String getTurnLanesString(RouteSegmentResult segment) {
@ -672,64 +672,44 @@ public class RouteResultPreparation {
} }
} }
private TurnType attachTurnLanesData(boolean leftSide, RouteSegmentResult prevSegm, TurnType t) { private int[] attachTurnLanesData(RouteSegmentResult prevSegm, int[] outgoingCalcLanes) {
int lanes = prevSegm.getObject().getLanes();
String turnLanes = getTurnLanesString(prevSegm); String turnLanes = getTurnLanesString(prevSegm);
if (turnLanes == null) { if (turnLanes == null) {
return t; return outgoingCalcLanes;
} }
String[] splitLaneOptions = turnLanes.split("\\|", -1); String[] splitLaneOptions = turnLanes.split("\\|", -1);
if (splitLaneOptions.length != lanes) { if (splitLaneOptions.length != countLanesMinOne(prevSegm)) {
// Error in data or missing data // Error in data or missing data
return t; return outgoingCalcLanes;
} }
int[] usableLanes = mergeOutgoingLanesUsingTurnLanes(outgoingCalcLanes, splitLaneOptions);
int[] rawLanes = calculateRawTurnLanes(splitLaneOptions, 0);
for(int k = 0 ; k < splitLaneOptions.length; k++) {
if((usableLanes[k] & 1) != 0) {
rawLanes[k] |= 1;
}
}
return rawLanes;
}
if (t.getLanes().length != lanes) { protected int[] mergeOutgoingLanesUsingTurnLanes(int[] outgoingCalcLanes, String[] splitLaneOptions) {
int[] usableLanes = outgoingCalcLanes;
if (outgoingCalcLanes.length != splitLaneOptions.length) {
usableLanes = new int[splitLaneOptions.length];
// The turn:lanes don't easily match up to the target road. // The turn:lanes don't easily match up to the target road.
List<Integer> sourceLanes = new ArrayList<Integer>();
int outgoingLanesIndex = 0; int outgoingLanesIndex = 0;
int sourceLanesIndex = 0; int sourceLanesIndex = 0;
while (outgoingLanesIndex < outgoingCalcLanes.length &&
while (outgoingLanesIndex < t.getLanes().length && sourceLanesIndex < lanes) { sourceLanesIndex < splitLaneOptions.length) {
if (splitLaneOptions[sourceLanesIndex].contains(";")) { int options = countOccurrences(splitLaneOptions[sourceLanesIndex], ';');
// Two or more allowed turns for this lane for (int k = 0; k <= options && outgoingLanesIndex < outgoingCalcLanes.length; k++) {
int options = countOccurrences(splitLaneOptions[sourceLanesIndex], ';'); usableLanes[sourceLanesIndex] |= outgoingCalcLanes[outgoingLanesIndex];
if (options == 1) {
if (outgoingLanesIndex + 1 >= t.getLanes().length) {
// Likely an error in data
return t;
}
int usability = t.getLanes()[outgoingLanesIndex] | t.getLanes()[outgoingLanesIndex + 1];
sourceLanes.add(usability);
outgoingLanesIndex += 2;
sourceLanesIndex++;
} else {
// Not supported
return t;
}
} else {
// Only one allowed turn; behave normally
sourceLanes.add(t.getLanes()[outgoingLanesIndex]);
outgoingLanesIndex++; outgoingLanesIndex++;
sourceLanesIndex++;
} }
sourceLanesIndex++;
} }
int[] newLanes = new int[sourceLanes.size()];
for (int i = 0; i < sourceLanes.size(); i++) {
newLanes[i] = sourceLanes.get(i);
}
t.setLanes(newLanes);
} }
return usableLanes;
assignTurns(splitLaneOptions, t);
t = inferTurnFromLanes(t, leftSide);
return t;
} }
private int countOccurrences(String haystack, char needle) { private int countOccurrences(String haystack, char needle) {
@ -742,7 +722,8 @@ public class RouteResultPreparation {
return count; return count;
} }
private void assignTurns(String[] splitLaneOptions, TurnType t) { private int[] calculateRawTurnLanes(String[] splitLaneOptions, int calcTurnType) {
int[] lanes = new int[splitLaneOptions.length];
for (int i = 0; i < splitLaneOptions.length; i++) { for (int i = 0; i < splitLaneOptions.length; i++) {
String[] laneOptions = splitLaneOptions[i].split(";"); String[] laneOptions = splitLaneOptions[i].split(";");
@ -769,80 +750,64 @@ public class RouteResultPreparation {
continue; continue;
} }
if (TurnType.getPrimaryTurn(t.getLanes()[i]) == 0) { if (TurnType.getPrimaryTurn(lanes[i]) == 0) {
t.setPrimaryTurn(i, turn); TurnType.setPrimaryTurn(lanes, i, turn);
} else { } else {
if (turn == t.getValue()) { if (turn == calcTurnType) {
t.setSecondaryTurn(i, TurnType.getPrimaryTurn(t.getLanes()[i])); TurnType.setSecondaryTurn(lanes, i, TurnType.getPrimaryTurn(lanes[i]));
t.setPrimaryTurn(i, turn); TurnType.setPrimaryTurn(lanes, i, turn);
} else { } else {
t.setSecondaryTurn(i, turn); TurnType.setSecondaryTurn(lanes, i, turn);
} }
break; // Move on to the next lane break; // Move on to the next lane
} }
} }
} }
return lanes;
} }
private TurnType inferTurnFromLanes(TurnType t, boolean leftSide) { private int inferTurnFromLanes(int[] oLanes) {
List<Integer> possibleTurns = new ArrayList<Integer>(); TIntHashSet possibleTurns = new TIntHashSet();
for (int i = 0; i < t.getLanes().length; i++) { for (int i = 0; i < oLanes.length; i++) {
if ((t.getLanes()[i] & 1) == 0) { if ((oLanes[i] & 1) == 0) {
continue; continue;
} }
if (possibleTurns.isEmpty()) { if (possibleTurns.isEmpty()) {
// Nothing is in the list to compare to, so add the first elements // Nothing is in the list to compare to, so add the first elements
possibleTurns.add(TurnType.getPrimaryTurn(t.getLanes()[i])); possibleTurns.add(TurnType.getPrimaryTurn(oLanes[i]));
if (TurnType.getSecondaryTurn(t.getLanes()[i]) != 0) { if (TurnType.getSecondaryTurn(oLanes[i]) != 0) {
possibleTurns.add(TurnType.getSecondaryTurn(t.getLanes()[i])); possibleTurns.add(TurnType.getSecondaryTurn(oLanes[i]));
} }
} else { } else {
List<Integer> laneTurns = new ArrayList<Integer>(); TIntArrayList laneTurns = new TIntArrayList();
laneTurns.add(TurnType.getPrimaryTurn(t.getLanes()[i])); laneTurns.add(TurnType.getPrimaryTurn(oLanes[i]));
if (TurnType.getSecondaryTurn(t.getLanes()[i]) != 0) { if (TurnType.getSecondaryTurn(oLanes[i]) != 0) {
laneTurns.add(TurnType.getSecondaryTurn(t.getLanes()[i])); laneTurns.add(TurnType.getSecondaryTurn(oLanes[i]));
} }
possibleTurns.retainAll(laneTurns); possibleTurns.retainAll(laneTurns);
if (possibleTurns.isEmpty()) { if (possibleTurns.isEmpty()) {
// No common turns, so can't determine anything. // No common turns, so can't determine anything.
return t; return 0;
} }
} }
} }
// Remove all turns from lanes not selected...because those aren't it // Remove all turns from lanes not selected...because those aren't it
for (int i = 0; i < t.getLanes().length; i++) { for (int i = 0; i < oLanes.length; i++) {
if ((t.getLanes()[i] & 1) == 0 && !possibleTurns.isEmpty()) { if ((oLanes[i] & 1) == 0 && !possibleTurns.isEmpty()) {
List<Integer> notLaneTurns = new ArrayList<Integer>(); possibleTurns.remove((Integer) TurnType.getPrimaryTurn(oLanes[i]));
notLaneTurns.add(TurnType.getPrimaryTurn(t.getLanes()[i])); if (TurnType.getSecondaryTurn(oLanes[i]) != 0) {
if (TurnType.getSecondaryTurn(t.getLanes()[i]) != 0) { possibleTurns.remove((Integer) TurnType.getSecondaryTurn(oLanes[i]));
notLaneTurns.add(TurnType.getSecondaryTurn(t.getLanes()[i]));
} }
possibleTurns.removeAll(notLaneTurns);
} }
} }
// Checking to see that there is only one unique turn // Checking to see that there is only one unique turn
if (new HashSet<Integer>(possibleTurns).size() == 1) { if (possibleTurns.size() == 1) {
TurnType derivedTurnType = TurnType.valueOf(possibleTurns.get(0), leftSide); return possibleTurns.iterator().next();
derivedTurnType.setLanes(t.getLanes());
derivedTurnType.setSkipToSpeak(t.isSkipToSpeak());
t = derivedTurnType;
// Because only the primary turn is displayed, if the turn to be taken is currently set as the secondary turn, then that needs to be switched around.
for (int i = 0; i < t.getLanes().length; i++) {
if (TurnType.getSecondaryTurn(t.getLanes()[i]) == t.getValue()) {
int temp = TurnType.getSecondaryTurn(t.getLanes()[i]);
t.setSecondaryTurn(i, TurnType.getPrimaryTurn(t.getLanes()[i]));
t.setPrimaryTurn(i, temp);
}
}
} }
return t; return 0;
} }
private boolean isMotorway(RouteSegmentResult s){ private boolean isMotorway(RouteSegmentResult s){

View file

@ -163,6 +163,10 @@ public class TurnType {
} }
// Note that there is no "weight" or ordering between the primary and secondary turns. // Note that there is no "weight" or ordering between the primary and secondary turns.
public static void setPrimaryTurn(int[] lanes, int lane, int turnType) {
lanes[lane] |= (turnType << 1);
}
public void setPrimaryTurn(int lane, int turnType) { public void setPrimaryTurn(int lane, int turnType) {
lanes[lane] |= (turnType << 1); lanes[lane] |= (turnType << 1);
} }
@ -172,6 +176,10 @@ public class TurnType {
return (laneValue >> 1) & ((1 << 4) - 1); return (laneValue >> 1) & ((1 << 4) - 1);
} }
public static void setSecondaryTurn(int[] lanes, int lane, int turnType) {
lanes[lane] |= (turnType << 5);
}
public void setSecondaryTurn(int lane, int turnType) { public void setSecondaryTurn(int lane, int turnType) {
lanes[lane] |= (turnType << 5); lanes[lane] |= (turnType << 5);
} }
@ -237,4 +245,12 @@ public class TurnType {
} }
return super.toString(); return super.toString();
} }
public static boolean isLeftTurn(int type) {
return type == TL || type == TSHL || type == TSLL;
}
public static boolean isRightTurn(int type) {
return type == TR || type == TSHR || type == TSLR;
}
} }