/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.routing;
import com.graphhopper.routing.util.DataFlagEncoder;
import com.graphhopper.routing.util.FlagEncoder;
import com.graphhopper.storage.NodeAccess;
import com.graphhopper.util.*;
import com.graphhopper.util.shapes.GHPoint;
import java.util.ArrayList;
import java.util.List;
/**
* This class maintains the surrounding edges for a single turn instruction.
* <p>
* There a different sets of edges.
* The previous edge is the edge we are comming from.
* The current edge is the edge we turn on.
* The reachable edges are all edges we could turn on, without the prev edge and the current edge.
* The surrounding edges are all edges surrounding the turn, without the prev edge and the current edge.
*
* @author Robin Boldt
*/
class InstructionsSurroundingEdges {
final EdgeIteratorState prevEdge;
final EdgeIteratorState currentEdge;
// Streets that are alternative turns, excluding oneways in the wrong direction
final List<EdgeIteratorState> reachableEdges;
// All Streets surrounding the turn, including oneways in the wrong direction
final List<EdgeIteratorState> surroundingEdges;
final FlagEncoder encoder;
final NodeAccess nodeAccess;
public InstructionsSurroundingEdges(EdgeIteratorState prevEdge,
EdgeIteratorState currentEdge,
FlagEncoder encoder,
EdgeExplorer crossingExplorer,
NodeAccess nodeAccess,
int prevNode,
int baseNode,
int adjNode) {
this.prevEdge = prevEdge;
this.currentEdge = currentEdge;
this.encoder = encoder;
this.nodeAccess = nodeAccess;
EdgeIteratorState tmpEdge;
surroundingEdges = new ArrayList<>();
reachableEdges = new ArrayList<>();
EdgeIterator edgeIter = crossingExplorer.setBaseNode(baseNode);
while (edgeIter.next()) {
if (edgeIter.getAdjNode() != prevNode && edgeIter.getAdjNode() != adjNode) {
tmpEdge = edgeIter.detach(false);
surroundingEdges.add(tmpEdge);
if (encoder.isForward(tmpEdge.getFlags())) {
reachableEdges.add(tmpEdge);
}
}
}
}
/**
* Calculates the Number of possible turns, including the current turn.
* If there is only one turn possible, e.g. continue straight on the road is a turn,
* the method will return 1.
*/
public int nrOfPossibleTurns() {
return 1 + reachableEdges.size();
}
/**
* Checks if the surrounding streets are slower. If they are, this indicates, that we are staying
* on the prominent street that one would follow anyway.
*/
public boolean surroundingStreetsAreSlowerByFactor(double factor) {
double tmpSpeed = getSpeed(currentEdge);
double pathSpeed = getSpeed(prevEdge);
// Speed-Change on the path indicates, that we change road types, show instruction
if (pathSpeed != tmpSpeed || pathSpeed < 1) {
return false;
}
double maxSurroundingSpeed = -1;
for (EdgeIteratorState edge : surroundingEdges) {
tmpSpeed = getSpeed(edge);
if (tmpSpeed < 1) {
// This might happen for the DataFlagEncoder, might create unnecessary turn instructions
return false;
}
if (tmpSpeed > maxSurroundingSpeed) {
maxSurroundingSpeed = tmpSpeed;
}
}
// Surrounding streets need to be slower by a factor
return maxSurroundingSpeed * factor < pathSpeed;
}
private double getSpeed(EdgeIteratorState edge) {
if (encoder instanceof DataFlagEncoder) {
return ((DataFlagEncoder) encoder).getMaxspeed(edge, 0, false);
} else {
return encoder.getSpeed(edge.getFlags());
}
}
/**
* Returns an edge that is going into more or less straight compared to the prevEdge.
* If there is none, return null.
*/
public EdgeIteratorState getOtherContinue(double prevLat, double prevLon, double prevOrientation) {
int tmpSign;
for (EdgeIteratorState edge : reachableEdges) {
GHPoint point = InstructionsHelper.getPointForOrientationCalculation(edge, nodeAccess);
tmpSign = InstructionsHelper.calculateSign(prevLat, prevLon, point.getLat(), point.getLon(), prevOrientation);
if (Math.abs(tmpSign) <= 1) {
return edge;
}
}
return null;
}
/**
* If the name and prevName changes this method checks if either the current street is continued on a
* different edge or if the edge we are turning onto is continued on a different edge.
* If either of these properties is true, we can be quite certain that a turn instruction should be provided.
*/
public boolean isLeavingCurrentStreet(String prevName, String name) {
if (InstructionsHelper.isNameSimilar(name, prevName)) {
return false;
}
// If flags are changing, there might be a chance we find these flags on a different edge
boolean checkFlag = currentEdge.getFlags() != prevEdge.getFlags();
for (EdgeIteratorState edge : reachableEdges) {
String edgeName = edge.getName();
long edgeFlag = edge.getFlags();
// leave the current street || enter a different street
if (isTheSameStreet(prevName, prevEdge.getFlags(), edgeName, edgeFlag, checkFlag)
|| isTheSameStreet(name, currentEdge.getFlags(), edgeName, edgeFlag, checkFlag)) {
return true;
}
}
return false;
}
private boolean isTheSameStreet(String name1, long flags1, String name2, long flags2, boolean checkFlag) {
if (InstructionsHelper.isNameSimilar(name1, name2)) {
if (!checkFlag || flags1 == flags2) {
return true;
}
}
return false;
}
}