/* * 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.util; import com.graphhopper.reader.ReaderRelation; import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.weighting.PriorityWeighting; import com.graphhopper.util.Helper; import com.graphhopper.util.InstructionAnnotation; import com.graphhopper.util.Translation; import java.util.*; import static com.graphhopper.routing.util.PriorityCode.*; /** * Defines bit layout of bicycles (not motorcycles) for speed, access and relations (network). * <p> * * @author Peter Karich * @author Nop * @author ratrun */ abstract public class BikeCommonFlagEncoder extends AbstractFlagEncoder { /** * Reports whether this edge is unpaved. */ public static final int K_UNPAVED = 100; protected static final int PUSHING_SECTION_SPEED = 4; // Pushing section heighways are parts where you need to get off your bike and push it (German: Schiebestrecke) protected final HashSet<String> pushingSectionsHighways = new HashSet<String>(); protected final HashSet<String> oppositeLanes = new HashSet<String>(); protected final Set<String> preferHighwayTags = new HashSet<String>(); protected final Set<String> avoidHighwayTags = new HashSet<String>(); protected final Set<String> unpavedSurfaceTags = new HashSet<String>(); private final Map<String, Integer> trackTypeSpeeds = new HashMap<String, Integer>(); private final Map<String, Integer> surfaceSpeeds = new HashMap<String, Integer>(); private final Set<String> roadValues = new HashSet<String>(); private final Map<String, Integer> highwaySpeeds = new HashMap<String, Integer>(); // convert network tag of bicycle routes into a way route code private final Map<String, Integer> bikeNetworkToCode = new HashMap<String, Integer>(); protected EncodedValue relationCodeEncoder; EncodedValue priorityWayEncoder; private long unpavedBit = 0; private EncodedValue wayTypeEncoder; // Car speed limit which switches the preference from UNCHANGED to AVOID_IF_POSSIBLE private int avoidSpeedLimit; // This is the specific bicycle class private String classBicycleKey; protected BikeCommonFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) { super(speedBits, speedFactor, maxTurnCosts); // strict set, usually vehicle and agricultural/forestry are ignored by cyclists restrictions.addAll(Arrays.asList("bicycle", "vehicle", "access")); restrictedValues.add("private"); restrictedValues.add("no"); restrictedValues.add("restricted"); restrictedValues.add("military"); restrictedValues.add("emergency"); intendedValues.add("yes"); intendedValues.add("designated"); intendedValues.add("official"); intendedValues.add("permissive"); oppositeLanes.add("opposite"); oppositeLanes.add("opposite_lane"); oppositeLanes.add("opposite_track"); setBlockByDefault(false); potentialBarriers.add("gate"); // potentialBarriers.add("lift_gate"); potentialBarriers.add("swing_gate"); absoluteBarriers.add("stile"); absoluteBarriers.add("turnstile"); unpavedSurfaceTags.add("unpaved"); unpavedSurfaceTags.add("gravel"); unpavedSurfaceTags.add("ground"); unpavedSurfaceTags.add("dirt"); unpavedSurfaceTags.add("grass"); unpavedSurfaceTags.add("compacted"); unpavedSurfaceTags.add("earth"); unpavedSurfaceTags.add("fine_gravel"); unpavedSurfaceTags.add("grass_paver"); unpavedSurfaceTags.add("ice"); unpavedSurfaceTags.add("mud"); unpavedSurfaceTags.add("salt"); unpavedSurfaceTags.add("sand"); unpavedSurfaceTags.add("wood"); roadValues.add("living_street"); roadValues.add("road"); roadValues.add("service"); roadValues.add("unclassified"); roadValues.add("residential"); roadValues.add("trunk"); roadValues.add("trunk_link"); roadValues.add("primary"); roadValues.add("primary_link"); roadValues.add("secondary"); roadValues.add("secondary_link"); roadValues.add("tertiary"); roadValues.add("tertiary_link"); maxPossibleSpeed = 30; setTrackTypeSpeed("grade1", 18); // paved setTrackTypeSpeed("grade2", 12); // now unpaved ... setTrackTypeSpeed("grade3", 8); setTrackTypeSpeed("grade4", 6); setTrackTypeSpeed("grade5", 4); // like sand/grass setSurfaceSpeed("paved", 18); setSurfaceSpeed("asphalt", 18); setSurfaceSpeed("cobblestone", 8); setSurfaceSpeed("cobblestone:flattened", 10); setSurfaceSpeed("sett", 10); setSurfaceSpeed("concrete", 18); setSurfaceSpeed("concrete:lanes", 16); setSurfaceSpeed("concrete:plates", 16); setSurfaceSpeed("paving_stones", 12); setSurfaceSpeed("paving_stones:30", 12); setSurfaceSpeed("unpaved", 14); setSurfaceSpeed("compacted", 16); setSurfaceSpeed("dirt", 10); setSurfaceSpeed("earth", 12); setSurfaceSpeed("fine_gravel", 18); setSurfaceSpeed("grass", 8); setSurfaceSpeed("grass_paver", 8); setSurfaceSpeed("gravel", 12); setSurfaceSpeed("ground", 12); setSurfaceSpeed("ice", PUSHING_SECTION_SPEED / 2); setSurfaceSpeed("metal", 10); setSurfaceSpeed("mud", 10); setSurfaceSpeed("pebblestone", 16); setSurfaceSpeed("salt", 6); setSurfaceSpeed("sand", 6); setSurfaceSpeed("wood", 6); setHighwaySpeed("living_street", 6); setHighwaySpeed("steps", PUSHING_SECTION_SPEED / 2); final int CYCLEWAY_SPEED = 18; // Make sure cycleway and path use same speed value, see #634 setHighwaySpeed("cycleway", CYCLEWAY_SPEED); setHighwaySpeed("path", 10); setHighwaySpeed("footway", 6); setHighwaySpeed("pedestrian", 6); setHighwaySpeed("track", 12); setHighwaySpeed("service", 14); setHighwaySpeed("residential", 18); // no other highway applies: setHighwaySpeed("unclassified", 16); // unknown road: setHighwaySpeed("road", 12); setHighwaySpeed("trunk", 18); setHighwaySpeed("trunk_link", 18); setHighwaySpeed("primary", 18); setHighwaySpeed("primary_link", 18); setHighwaySpeed("secondary", 18); setHighwaySpeed("secondary_link", 18); setHighwaySpeed("tertiary", 18); setHighwaySpeed("tertiary_link", 18); // special case see tests and #191 setHighwaySpeed("motorway", 18); setHighwaySpeed("motorway_link", 18); avoidHighwayTags.add("motorway"); avoidHighwayTags.add("motorway_link"); setCyclingNetworkPreference("icn", BEST.getValue()); setCyclingNetworkPreference("ncn", BEST.getValue()); setCyclingNetworkPreference("rcn", VERY_NICE.getValue()); setCyclingNetworkPreference("lcn", PREFER.getValue()); setCyclingNetworkPreference("mtb", UNCHANGED.getValue()); setCyclingNetworkPreference("deprecated", AVOID_AT_ALL_COSTS.getValue()); setAvoidSpeedLimit(71); } @Override public int getVersion() { return 2; } @Override public int defineWayBits(int index, int shift) { // first two bits are reserved for route handling in superclass shift = super.defineWayBits(index, shift); speedEncoder = new EncodedDoubleValue("Speed", shift, speedBits, speedFactor, highwaySpeeds.get("cycleway"), maxPossibleSpeed); shift += speedEncoder.getBits(); unpavedBit = 1L << shift++; // 2 bits wayTypeEncoder = new EncodedValue("WayType", shift, 2, 1, 0, 3, true); shift += wayTypeEncoder.getBits(); priorityWayEncoder = new EncodedValue("PreferWay", shift, 3, 1, 0, 7); shift += priorityWayEncoder.getBits(); return shift; } @Override public int defineRelationBits(int index, int shift) { relationCodeEncoder = new EncodedValue("RelationCode", shift, 3, 1, 0, 7); return shift + relationCodeEncoder.getBits(); } @Override public long acceptWay(ReaderWay way) { String highwayValue = way.getTag("highway"); if (highwayValue == null) { if (way.hasTag("route", ferries)) { // if bike is NOT explicitly tagged allow bike but only if foot is not specified String bikeTag = way.getTag("bicycle"); if (bikeTag == null && !way.hasTag("foot") || "yes".equals(bikeTag)) return acceptBit | ferryBit; } // special case not for all acceptedRailways, only platform if (way.hasTag("railway", "platform")) return acceptBit; return 0; } if (!highwaySpeeds.containsKey(highwayValue)) return 0; String sacScale = way.getTag("sac_scale"); if (sacScale != null) { if ((way.hasTag("highway", "cycleway")) && (way.hasTag("sac_scale", "hiking"))) return acceptBit; if (!isSacScaleAllowed(sacScale)) return 0; } // use the way if it is tagged for bikes if (way.hasTag("bicycle", intendedValues) || way.hasTag("bicycle", "dismount") || way.hasTag("highway", "cycleway")) return acceptBit; // accept only if explicitly tagged for bike usage if ("motorway".equals(highwayValue) || "motorway_link".equals(highwayValue)) return 0; if (way.hasTag("motorroad", "yes")) return 0; // do not use fords with normal bikes, flagged fords are in included above if (isBlockFords() && (way.hasTag("highway", "ford") || way.hasTag("ford"))) return 0; // check access restrictions if (way.hasTag(restrictions, restrictedValues) && !getConditionalTagInspector().isRestrictedWayConditionallyPermitted(way)) return 0; if (getConditionalTagInspector().isPermittedWayConditionallyRestricted(way)) return 0; else return acceptBit; } boolean isSacScaleAllowed(String sacScale) { // other scales are nearly impossible by an ordinary bike, see http://wiki.openstreetmap.org/wiki/Key:sac_scale return "hiking".equals(sacScale); } @Override public long handleRelationTags(ReaderRelation relation, long oldRelationFlags) { int code = 0; if (relation.hasTag("route", "bicycle")) { Integer val = bikeNetworkToCode.get(relation.getTag("network")); if (val != null) code = val; else code = PriorityCode.PREFER.getValue(); // Assume priority of network "lcn" as bicycle route default } else if (relation.hasTag("route", "ferry")) { code = AVOID_IF_POSSIBLE.getValue(); } int oldCode = (int) relationCodeEncoder.getValue(oldRelationFlags); if (oldCode < code) return relationCodeEncoder.setValue(0, code); return oldRelationFlags; } /** * Apply maxspeed: In contrast to the implementation of the AbstractFlagEncoder, we assume that * we can reach the maxspeed for bicycles in case that the road type speed is higher and not * just only 90%. * <p> * * @param way: needed to retrieve tags * @param speed: speed guessed e.g. from the road type or other tags * @return The assumed average speed. */ @Override protected double applyMaxSpeed(ReaderWay way, double speed) { double maxSpeed = getMaxSpeed(way); if (maxSpeed >= 0) { // We strictly obay speed limits, see #600 if (maxSpeed < speed) { return maxSpeed; } } return speed; } @Override public long handleWayTags(ReaderWay way, long allowed, long relationFlags) { if (!isAccept(allowed)) return 0; long flags = 0; double wayTypeSpeed = getSpeed(way); if (!isFerry(allowed)) { wayTypeSpeed = applyMaxSpeed(way, wayTypeSpeed); flags = handleSpeed(way, wayTypeSpeed, flags); flags = handleBikeRelated(way, flags, relationFlags > UNCHANGED.getValue()); boolean isRoundabout = way.hasTag("junction", "roundabout"); if (isRoundabout) { flags = setBool(flags, K_ROUNDABOUT, true); } } else { double ferrySpeed = getFerrySpeed(way, highwaySpeeds.get("living_street"), highwaySpeeds.get("track"), highwaySpeeds.get("primary")); flags = handleSpeed(way, ferrySpeed, flags); flags |= directionBitMask; } int priorityFromRelation = 0; if (relationFlags != 0) priorityFromRelation = (int) relationCodeEncoder.getValue(relationFlags); flags = priorityWayEncoder.setValue(flags, handlePriority(way, wayTypeSpeed, priorityFromRelation)); return flags; } int getSpeed(ReaderWay way) { int speed = PUSHING_SECTION_SPEED; String highwayTag = way.getTag("highway"); Integer highwaySpeed = highwaySpeeds.get(highwayTag); // Under certain conditions we need to increase the speed of pushing sections to the speed of a "highway=cycleway" if (way.hasTag("highway", pushingSectionsHighways) && ((way.hasTag("foot", "yes") && way.hasTag("segregated", "yes")) || way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official"))) highwaySpeed = getHighwaySpeed("cycleway"); String s = way.getTag("surface"); if (!Helper.isEmpty(s)) { Integer surfaceSpeed = surfaceSpeeds.get(s); if (surfaceSpeed != null) { speed = surfaceSpeed; // boost handling for good surfaces but avoid boosting if pushing section if (highwaySpeed != null && surfaceSpeed > highwaySpeed) { if (pushingSectionsHighways.contains(highwayTag)) speed = highwaySpeed; else speed = surfaceSpeed; } } } else { String tt = way.getTag("tracktype"); if (!Helper.isEmpty(tt)) { Integer tInt = trackTypeSpeeds.get(tt); if (tInt != null) speed = tInt; } else if (highwaySpeed != null) { if (!way.hasTag("service")) speed = highwaySpeed; else speed = highwaySpeeds.get("living_street"); } } // Until now we assumed that the way is no pushing section // Now we check that, but only in case that our speed is bigger compared to the PUSHING_SECTION_SPEED if (speed > PUSHING_SECTION_SPEED && (way.hasTag("highway", pushingSectionsHighways) || way.hasTag("bicycle", "dismount"))) { if (!way.hasTag("bicycle", intendedValues)) { // Here we set the speed for pushing sections and set speed for steps as even lower: if (way.hasTag("highway", "steps")) speed = PUSHING_SECTION_SPEED / 2; else speed = PUSHING_SECTION_SPEED; } else if (way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official")) { // Here we handle the cases where the OSM tagging results in something similar to "highway=cycleway" speed = highwaySpeeds.get("cycleway"); } else { speed = PUSHING_SECTION_SPEED; } // Increase speed in case of segregated if (speed <= PUSHING_SECTION_SPEED && way.hasTag("segregated", "yes")) speed = PUSHING_SECTION_SPEED * 2; } return speed; } @Override public InstructionAnnotation getAnnotation(long flags, Translation tr) { int paveType = 0; // paved if (isBool(flags, K_UNPAVED)) paveType = 1; // unpaved int wayType = (int) wayTypeEncoder.getValue(flags); String wayName = getWayName(paveType, wayType, tr); return new InstructionAnnotation(0, wayName); } String getWayName(int pavementType, int wayType, Translation tr) { String pavementName = ""; if (pavementType == 1) pavementName = tr.tr("unpaved"); String wayTypeName = ""; switch (wayType) { case 0: wayTypeName = ""; break; case 1: wayTypeName = tr.tr("off_bike"); break; case 2: wayTypeName = tr.tr("cycleway"); break; case 3: wayTypeName = tr.tr("small_way"); break; } if (pavementName.isEmpty()) { if (wayType == 0 || wayType == 3) return ""; return wayTypeName; } else if (wayTypeName.isEmpty()) return pavementName; else return wayTypeName + ", " + pavementName; } /** * In this method we prefer cycleways or roads with designated bike access and avoid big roads * or roads with trams or pedestrian. * <p> * * @return new priority based on priorityFromRelation and on the tags in ReaderWay. */ protected int handlePriority(ReaderWay way, double wayTypeSpeed, int priorityFromRelation) { TreeMap<Double, Integer> weightToPrioMap = new TreeMap<Double, Integer>(); if (priorityFromRelation == 0) weightToPrioMap.put(0d, UNCHANGED.getValue()); else weightToPrioMap.put(110d, priorityFromRelation); collect(way, wayTypeSpeed, weightToPrioMap); // pick priority with biggest order value return weightToPrioMap.lastEntry().getValue(); } // Conversion of class value to priority. See http://wiki.openstreetmap.org/wiki/Class:bicycle private PriorityCode convertClassValueToPriority(String tagvalue) { int classvalue; try { classvalue = Integer.parseInt(tagvalue); } catch (NumberFormatException e) { return UNCHANGED; } switch (classvalue) { case 3: return BEST; case 2: return VERY_NICE; case 1: return PREFER; case 0: return UNCHANGED; case -1: return AVOID_IF_POSSIBLE; case -2: return REACH_DEST; case -3: return AVOID_AT_ALL_COSTS; default: return UNCHANGED; } } /** * @param weightToPrioMap associate a weight with every priority. This sorted map allows * subclasses to 'insert' more important priorities as well as overwrite determined priorities. */ void collect(ReaderWay way, double wayTypeSpeed, TreeMap<Double, Integer> weightToPrioMap) { String service = way.getTag("service"); String highway = way.getTag("highway"); if (way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official")) { if ("path".equals(highway)) weightToPrioMap.put(100d, VERY_NICE.getValue()); else weightToPrioMap.put(100d, PREFER.getValue()); } if ("cycleway".equals(highway)) { if (way.hasTag("foot", intendedValues) && !way.hasTag("segregated", "yes")) weightToPrioMap.put(100d, PREFER.getValue()); else weightToPrioMap.put(100d, VERY_NICE.getValue()); } double maxSpeed = getMaxSpeed(way); if (preferHighwayTags.contains(highway) || maxSpeed > 0 && maxSpeed <= 30) { if (maxSpeed < avoidSpeedLimit) { weightToPrioMap.put(40d, PREFER.getValue()); if (way.hasTag("tunnel", intendedValues)) weightToPrioMap.put(40d, UNCHANGED.getValue()); } } else if (avoidHighwayTags.contains(highway) || maxSpeed >= avoidSpeedLimit && !"track".equals(highway)) { weightToPrioMap.put(50d, REACH_DEST.getValue()); if (way.hasTag("tunnel", intendedValues)) weightToPrioMap.put(50d, AVOID_AT_ALL_COSTS.getValue()); } if (pushingSectionsHighways.contains(highway) || way.hasTag("bicycle", "use_sidepath") || "parking_aisle".equals(service)) { int pushingSectionPrio = AVOID_IF_POSSIBLE.getValue(); if (way.hasTag("bicycle", "yes") || way.hasTag("bicycle", "permissive")) pushingSectionPrio = PREFER.getValue(); if (way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official")) pushingSectionPrio = VERY_NICE.getValue(); if (way.hasTag("foot", "yes")) { pushingSectionPrio = Math.max(pushingSectionPrio - 1, WORST.getValue()); if (way.hasTag("segregated", "yes")) pushingSectionPrio = Math.min(pushingSectionPrio + 1, BEST.getValue()); } weightToPrioMap.put(100d, pushingSectionPrio); } if (way.hasTag("railway", "tram")) weightToPrioMap.put(50d, AVOID_AT_ALL_COSTS.getValue()); String classBicycleValue = way.getTag(classBicycleKey); if (classBicycleValue != null) { // We assume that humans are better in classifying preferences compared to our algorithm above -> weight = 100 weightToPrioMap.put(100d, convertClassValueToPriority(classBicycleValue).getValue()); } else { String classBicycle = way.getTag("class:bicycle"); if (classBicycle != null) weightToPrioMap.put(100d, convertClassValueToPriority(classBicycle).getValue()); } // Increase the priority for scenic routes or in case that maxspeed limits our average speed as compensation. See #630 if (way.hasTag("scenic", "yes") || maxSpeed > 0 && maxSpeed < wayTypeSpeed) { if (weightToPrioMap.lastEntry().getValue() < BEST.getValue()) // Increase the prio by one step weightToPrioMap.put(110d, weightToPrioMap.lastEntry().getValue() + 1); } } /** * Handle surface and wayType encoding */ long handleBikeRelated(ReaderWay way, long encoded, boolean partOfCycleRelation) { String surfaceTag = way.getTag("surface"); String highway = way.getTag("highway"); String trackType = way.getTag("tracktype"); // Populate unpavedBit if ("track".equals(highway) && (trackType == null || !"grade1".equals(trackType)) || "path".equals(highway) && surfaceTag == null || unpavedSurfaceTags.contains(surfaceTag)) { encoded = setBool(encoded, K_UNPAVED, true); } WayType wayType; if (roadValues.contains(highway)) wayType = WayType.ROAD; else wayType = WayType.OTHER_SMALL_WAY; boolean isPushingSection = isPushingSection(way); if (isPushingSection && !partOfCycleRelation || "steps".equals(highway)) wayType = WayType.PUSHING_SECTION; if (way.hasTag("bicycle", intendedValues)) { if (isPushingSection && !way.hasTag("bicycle", "designated")) wayType = WayType.OTHER_SMALL_WAY; else if (wayType == WayType.OTHER_SMALL_WAY || wayType == WayType.PUSHING_SECTION) wayType = WayType.CYCLEWAY; } else if ("cycleway".equals(highway)) wayType = WayType.CYCLEWAY; return wayTypeEncoder.setValue(encoded, wayType.getValue()); } @Override public long setBool(long flags, int key, boolean value) { switch (key) { case K_UNPAVED: return value ? flags | unpavedBit : flags & ~unpavedBit; default: return super.setBool(flags, key, value); } } @Override public boolean isBool(long flags, int key) { switch (key) { case K_UNPAVED: return (flags & unpavedBit) != 0; default: return super.isBool(flags, key); } } @Override public double getDouble(long flags, int key) { switch (key) { case PriorityWeighting.KEY: return (double) priorityWayEncoder.getValue(flags) / BEST.getValue(); default: return super.getDouble(flags, key); } } boolean isPushingSection(ReaderWay way) { return way.hasTag("highway", pushingSectionsHighways) || way.hasTag("railway", "platform") || way.hasTag("bicycle", "dismount"); } protected long handleSpeed(ReaderWay way, double speed, long encoded) { encoded = setSpeed(encoded, speed); // handle oneways boolean isOneway = way.hasTag("oneway", oneways) || way.hasTag("oneway:bicycle", oneways) || way.hasTag("vehicle:backward") || way.hasTag("vehicle:forward") || way.hasTag("bicycle:forward"); if ((isOneway || way.hasTag("junction", "roundabout")) && !way.hasTag("oneway:bicycle", "no") && !way.hasTag("bicycle:backward") && !way.hasTag("cycleway", oppositeLanes) && !way.hasTag("cycleway:left", oppositeLanes) && !way.hasTag("cycleway:right", oppositeLanes)) { boolean isBackward = way.hasTag("oneway", "-1") || way.hasTag("oneway:bicycle", "-1") || way.hasTag("vehicle:forward", "no") || way.hasTag("bicycle:forward", "no"); if (isBackward) encoded |= backwardBit; else encoded |= forwardBit; } else { encoded |= directionBitMask; } return encoded; } protected void setHighwaySpeed(String highway, int speed) { highwaySpeeds.put(highway, speed); } protected int getHighwaySpeed(String key) { return highwaySpeeds.get(key); } void setTrackTypeSpeed(String tracktype, int speed) { trackTypeSpeeds.put(tracktype, speed); } void setSurfaceSpeed(String surface, int speed) { surfaceSpeeds.put(surface, speed); } void setCyclingNetworkPreference(String network, int code) { bikeNetworkToCode.put(network, code); } void addPushingSection(String highway) { pushingSectionsHighways.add(highway); } @Override public boolean supports(Class<?> feature) { if (super.supports(feature)) return true; return PriorityWeighting.class.isAssignableFrom(feature); } public void setAvoidSpeedLimit(int limit) { avoidSpeedLimit = limit; } protected void setSpecificClassBicycle(String subkey) { classBicycleKey = "class:bicycle:" + subkey; } private enum WayType { ROAD(0), PUSHING_SECTION(1), CYCLEWAY(2), OTHER_SMALL_WAY(3); private final int value; private WayType(int value) { this.value = value; } public int getValue() { return value; } } }