FootFlagEncoder.java
/*
* This file is part of Openrouteservice.
*
* Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library;
* if not, see <https://www.gnu.org/licenses/>.
*/
package org.heigit.ors.routing.graphhopper.extensions.flagencoders;
import com.graphhopper.reader.ReaderWay;
import com.graphhopper.routing.ev.*;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.routing.util.TransportationMode;
import com.graphhopper.routing.weighting.PriorityWeighting;
import com.graphhopper.storage.ConditionalEdges;
import com.graphhopper.storage.IntsRef;
import com.graphhopper.util.PMap;
import org.heigit.ors.routing.graphhopper.extensions.OSMTags;
import org.heigit.ors.routing.graphhopper.extensions.util.PriorityCode;
import java.util.*;
import static com.graphhopper.routing.ev.RouteNetwork.*;
import static com.graphhopper.routing.util.EncodingManager.getKey;
import static org.heigit.ors.routing.graphhopper.extensions.util.PriorityCode.*;
/**
* This code has been adapted from the original GraphHopper FootFlagEncoder found at
* https://github.com/graphhopper/graphhopper/blob/master/core/src/main/java/com/graphhopper/routing/util/FootFlagEncoder.java
*
* @author Adam Rousell
* @author Peter Karich
* @author Nop
* @author Karl Hübner
*/
public abstract class FootFlagEncoder extends com.graphhopper.routing.util.FootFlagEncoder {
static final int SLOW_SPEED = 2;
private static final int MEAN_SPEED = 5;
static final int FERRY_SPEED = 15;
public static final String KEY_DESIGNATED = "designated";
private final Set<String> safeHighwayTags = new HashSet<>();
private final Set<String> allowedHighwayTags = new HashSet<>();
private final Set<String> avoidHighwayTags = new HashSet<>();
Set<String> preferredWayTags = new HashSet<>();
private final Set<String> avoidUnlessSidewalkTags = new HashSet<>();
Set<String> suitableSacScales = new HashSet<>();
// convert network tag of hiking routes into a way route code
Set<String> usableSidewalkValues = new HashSet<>(5);
Set<String> noSidewalkValues = new HashSet<>(5);
protected DecimalEncodedValue priorityWayEncoder;
protected EnumEncodedValue<RouteNetwork> footRouteEnc;
Map<RouteNetwork, Integer> routeMap = new HashMap<>();
private BooleanEncodedValue conditionalAccessEncoder;
protected void setProperties(PMap properties) {
this.setProperties(properties, true);
}
protected void setProperties(PMap properties, boolean blockFords) {
this.properties = properties;
this.blockFords(properties.getBool("block_fords", blockFords));
}
FootFlagEncoder(int speedBits, double speedFactor) {
super(speedBits, speedFactor);
restrictions.addAll(Arrays.asList("foot", "access"));
restrictedValues.addAll(Arrays.asList(
"private",
"no",
"restricted",
"military",
"emergency"
));
intendedValues.addAll(Arrays.asList(
"yes",
KEY_DESIGNATED,
"official",
"permissive"
));
noSidewalkValues.addAll(Arrays.asList(
"no",
"none",
"separate",
"separate"
));
usableSidewalkValues.addAll(Arrays.asList(
"yes",
"both",
"left",
"right"
));
blockByDefaultBarriers.add("fence");
passByDefaultBarriers.add("gate");
passByDefaultBarriers.add("cattle_grid");
safeHighwayTags.addAll(Arrays.asList(
"footway",
"path",
"steps",
"pedestrian",
"living_street",
"track",
"residential",
"service"
));
avoidHighwayTags.addAll(Arrays.asList(
"secondary",
"secondary_link",
"tertiary",
"tertiary_link"
));
avoidUnlessSidewalkTags.addAll(Arrays.asList(
"trunk",
"trunk_link",
"primary",
"primary_link"
));
allowedHighwayTags.addAll(safeHighwayTags);
allowedHighwayTags.addAll(avoidHighwayTags);
allowedHighwayTags.addAll(avoidUnlessSidewalkTags);
allowedHighwayTags.addAll(Arrays.asList(
"cycleway",
"unclassified",
"road"
));
routeMap.put(INTERNATIONAL, UNCHANGED.getValue());
routeMap.put(NATIONAL, UNCHANGED.getValue());
routeMap.put(REGIONAL, UNCHANGED.getValue());
routeMap.put(LOCAL, UNCHANGED.getValue());
routeMap.put(FERRY, AVOID_IF_POSSIBLE.getValue());
maxPossibleSpeed = FERRY_SPEED;
}
public double getMeanSpeed() {
return MEAN_SPEED;
}
@Override
public void createEncodedValues(List<EncodedValue> registerNewEncodedValue, String prefix, int index) {
// first two bits are reserved for route handling in superclass
super.createEncodedValues(registerNewEncodedValue, prefix, index);
// larger value required - ferries are faster than pedestrians
registerNewEncodedValue.add(avgSpeedEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "average_speed"), speedBits, speedFactor, false));
priorityWayEncoder = new UnsignedDecimalEncodedValue(getKey(prefix, FlagEncoderKeys.PRIORITY_KEY), 4, PriorityCode.getFactor(1), false);
registerNewEncodedValue.add(priorityWayEncoder);
if (properties.getBool(ConditionalEdges.ACCESS, false)) {
conditionalAccessEncoder = new SimpleBooleanEncodedValue(EncodingManager.getKey(prefix, ConditionalEdges.ACCESS), true);
registerNewEncodedValue.add(conditionalAccessEncoder);
}
footRouteEnc = getEnumEncodedValue(RouteNetwork.key("foot"), RouteNetwork.class);
}
@Override
public EncodingManager.Access getAccess(ReaderWay way) {
String highwayValue = way.getTag(OSMTags.Keys.HIGHWAY);
if (highwayValue == null)
return handleNonHighways(way);
if (hasTooDifficultSacScale(way))
return EncodingManager.Access.CAN_SKIP;
// no need to evaluate ferries or fords - already included here
if (way.hasTag(OSMTags.Keys.FOOT, intendedValues))
return isPermittedWayConditionallyRestricted(way);
// check access restrictions
if (way.hasTag(restrictions, restrictedValues))
return isRestrictedWayConditionallyPermitted(way);
if (way.hasTag(OSMTags.Keys.SIDEWALK, usableSidewalkValues))
return isPermittedWayConditionallyRestricted(way);
if (!allowedHighwayTags.contains(highwayValue))
return EncodingManager.Access.CAN_SKIP;
if (way.hasTag(OSMTags.Keys.MOTOR_ROAD, "yes"))
return EncodingManager.Access.CAN_SKIP;
// do not get our feet wet, "yes" is already included above
if (isBlockFords() && (way.hasTag(OSMTags.Keys.HIGHWAY, "ford") || way.hasTag(OSMTags.Keys.FORD)))
return EncodingManager.Access.CAN_SKIP;
if (getConditionalTagInspector().isPermittedWayConditionallyRestricted(way))
return EncodingManager.Access.CAN_SKIP;
return isPermittedWayConditionallyRestricted(way);
}
@Override
public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.Access access) {
return handleWayTags(edgeFlags, way, access, null);
}
public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.Access access, IntsRef relationFlags) {
if (access.canSkip())
return edgeFlags;
Integer priorityFromRelation = routeMap.get(footRouteEnc.getEnum(false, edgeFlags));
if (!access.isFerry()) {
String sacScale = way.getTag(OSMTags.Keys.SAC_SCALE);
if (sacScale != null && !"hiking".equals(sacScale)) {
avgSpeedEnc.setDecimal(false, edgeFlags, SLOW_SPEED);
} else {
avgSpeedEnc.setDecimal(false, edgeFlags, MEAN_SPEED);
}
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
if (access.isConditional() && conditionalAccessEncoder != null)
conditionalAccessEncoder.setBool(false, edgeFlags, true);
} else {
double ferrySpeed = ferrySpeedCalc.getSpeed(way);
setSpeed(false, edgeFlags, ferrySpeed);
}
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
priorityWayEncoder.setDecimal(false, edgeFlags, PriorityCode.getFactor(handlePriority(way, priorityFromRelation != null ? priorityFromRelation : 0)));
return edgeFlags;
}
/**
* Method which generates the acceptance flag for ways that are not seen as being highways (such as ferry routes)
*
* @param way The way that is to be assessed
* @return The acceptance flag for the way
*/
private EncodingManager.Access handleNonHighways(ReaderWay way) {
EncodingManager.Access acceptPotentially = EncodingManager.Access.CAN_SKIP;
if (way.hasTag(OSMTags.Keys.ROUTE, ferries)) {
String footTag = way.getTag(OSMTags.Keys.FOOT);
if (footTag == null || intendedValues.contains(footTag))
acceptPotentially = EncodingManager.Access.FERRY;
}
// special case not for all acceptedRailways, only platform
if (way.hasTag(OSMTags.Keys.RAILWAY, "platform"))
acceptPotentially = EncodingManager.Access.WAY;
if (way.hasTag(OSMTags.Keys.MAN_MADE, "pier"))
acceptPotentially = EncodingManager.Access.WAY;
// only route via lock_gate if foot-tag allows for it.
if (way.hasTag(OSMTags.Keys.WATERWAY, "lock_gate")) {
if (way.hasTag(OSMTags.Keys.FOOT, intendedValues)) {
acceptPotentially = EncodingManager.Access.WAY;
}
}
if (!acceptPotentially.canSkip()) {
if (way.hasTag(restrictions, restrictedValues))
return isRestrictedWayConditionallyPermitted(way, acceptPotentially);
return isPermittedWayConditionallyRestricted(way, acceptPotentially);
}
return EncodingManager.Access.CAN_SKIP;
}
/**
* Determine if the way is seen as being too difficult based on any sac_scale tags and the information provided in
* the setup of the object (suitableSacScales)
*
* @param way The way to be assessed
* @return Whether the way is too difficult or not
*/
private boolean hasTooDifficultSacScale(ReaderWay way) {
String sacScale = way.getTag(OSMTags.Keys.SAC_SCALE);
return sacScale != null && !suitableSacScales.contains(sacScale);
}
/**
* Assign priorities based on relations and values stored against the way. This is the top level method that calls
* other priority assessment methods
*
* @param way The way to be assessed
* @param priorityFromRelation The priority obtained from any relations
* @return The overall priority value for the way
*/
protected int handlePriority(ReaderWay way, int priorityFromRelation) {
TreeMap<Double, Integer> weightToPrioMap = new TreeMap<>();
if (priorityFromRelation == 0)
weightToPrioMap.put(0d, UNCHANGED.getValue());
else
weightToPrioMap.put(110d, priorityFromRelation);
assignPriorities(way, weightToPrioMap);
// pick priority with biggest order value
return weightToPrioMap.lastEntry().getValue();
}
/**
* @param weightToPrioMap associate a weight with every priority. This sorted map allows
* subclasses to 'insert' more important priorities as well as overwrite determined priorities.
*/
private void assignPriorities(ReaderWay way, TreeMap<Double, Integer> weightToPrioMap) {
if (way.hasTag(OSMTags.Keys.FOOT, KEY_DESIGNATED))
weightToPrioMap.put(100d, PREFER.getValue());
assignSafeHighwayPriority(way, weightToPrioMap);
assignAvoidHighwayPriority(way, weightToPrioMap);
assignAvoidUnlessSidewalkPresentPriority(way, weightToPrioMap);
assignBicycleWayPriority(way, weightToPrioMap);
}
/**
* Update the weight priority map based on values relating to highway types that are identified as being "safe" or
* with low speeds
*
* @param way The way containing the tag information
* @param weightToPrioMap The priority weight map that will have the weightings updated
*/
void assignSafeHighwayPriority(ReaderWay way, TreeMap<Double, Integer> weightToPrioMap) {
String highway = way.getTag(OSMTags.Keys.HIGHWAY);
double maxSpeed = getMaxSpeed(way);
if (safeHighwayTags.contains(highway) || isValidSpeed(maxSpeed) && maxSpeed <= 20) {
if (preferredWayTags.contains(highway))
weightToPrioMap.put(40d, VERY_NICE.getValue());
else {
weightToPrioMap.put(40d, PREFER.getValue());
}
assignTunnelPriority(way, weightToPrioMap);
}
}
/**
* Update the weight priority map based on tunnel information
*
* @param way The way containing the tag information
* @param weightToPrioMap The priority weight map that will have the weightings updated
*/
void assignTunnelPriority(ReaderWay way, TreeMap<Double, Integer> weightToPrioMap) {
if (way.hasTag(OSMTags.Keys.TUNNEL, intendedValues)) {
if (way.hasTag(OSMTags.Keys.SIDEWALK, noSidewalkValues))
weightToPrioMap.put(40d, AVOID_IF_POSSIBLE.getValue());
else
weightToPrioMap.put(40d, UNCHANGED.getValue());
}
}
/**
* Update the weight priority map based on values relating to avoiding highways
*
* @param way The way containing the tag information
* @param weightToPrioMap The priority weight map that will have the weightings updated
*/
private void assignAvoidHighwayPriority(ReaderWay way, TreeMap<Double, Integer> weightToPrioMap) {
String highway = way.getTag(OSMTags.Keys.HIGHWAY);
double maxSpeed = getMaxSpeed(way);
if ((maxSpeed > 50 || avoidHighwayTags.contains(highway))
&& !way.hasTag(OSMTags.Keys.SIDEWALK, usableSidewalkValues)) {
weightToPrioMap.put(45d, REACH_DEST.getValue());
}
}
/**
* Mark the way as to be avoided if there is no sidewalk present on highway types identified as needing a sidewalk
* to be traversed
*
* @param way The way containing the tag information
* @param weightToPrioMap The priority weight map that will have the weightings updated
*/
private void assignAvoidUnlessSidewalkPresentPriority(ReaderWay way, TreeMap<Double, Integer> weightToPrioMap) {
String highway = way.getTag(OSMTags.Keys.HIGHWAY);
if (avoidUnlessSidewalkTags.contains(highway) && !way.hasTag(OSMTags.Keys.SIDEWALK, usableSidewalkValues))
weightToPrioMap.put(45d, AVOID_AT_ALL_COSTS.getValue());
}
/**
* Update the weight priority map based on values relating to bicycle ways.
*
* @param way The way containing the tag information
* @param weightToPrioMap The priority weight map that will have the weightings updated
*/
private void assignBicycleWayPriority(ReaderWay way, TreeMap<Double, Integer> weightToPrioMap) {
if (way.hasTag(OSMTags.Keys.BICYCLE, "official") || way.hasTag(OSMTags.Keys.BICYCLE, KEY_DESIGNATED))
weightToPrioMap.put(44d, AVOID_IF_POSSIBLE.getValue());
}
@Override
public boolean supports(Class<?> feature) {
if (super.supports(feature)) {
return true;
}
return PriorityWeighting.class.isAssignableFrom(feature);
}
@Override
public TransportationMode getTransportationMode() {
return TransportationMode.FOOT;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final FootFlagEncoder other = (FootFlagEncoder) obj;
return toString().equals(other.toString());
}
@Override
public int hashCode() {
return ("FootFlagEncoder" + this).hashCode();
}
}