/*
* 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.ConditionalTagInspector;
import com.graphhopper.reader.ReaderNode;
import com.graphhopper.reader.ReaderRelation;
import com.graphhopper.reader.ReaderWay;
import com.graphhopper.reader.osm.conditional.ConditionalOSMTagInspector;
import com.graphhopper.reader.osm.conditional.DateRangeParser;
import com.graphhopper.routing.weighting.TurnWeighting;
import com.graphhopper.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Abstract class which handles flag decoding and encoding. Every encoder should be registered to a
* EncodingManager to be usable. If you want the full long to be stored you need to enable this in
* the GraphHopperStorage.
* <p>
*
* @author Peter Karich
* @author Nop
* @see EncodingManager
*/
public abstract class AbstractFlagEncoder implements FlagEncoder, TurnCostEncoder {
protected final static int K_FORWARD = 0, K_BACKWARD = 1;
private final static Logger logger = LoggerFactory.getLogger(AbstractFlagEncoder.class);
/* restriction definitions where order is important */
protected final List<String> restrictions = new ArrayList<String>(5);
protected final Set<String> intendedValues = new HashSet<String>(5);
protected final Set<String> restrictedValues = new HashSet<String>(5);
protected final Set<String> ferries = new HashSet<String>(5);
protected final Set<String> oneways = new HashSet<String>(5);
// http://wiki.openstreetmap.org/wiki/Mapfeatures#Barrier
protected final Set<String> absoluteBarriers = new HashSet<String>(5);
protected final Set<String> potentialBarriers = new HashSet<String>(5);
protected final int speedBits;
protected final double speedFactor;
private final int maxTurnCosts;
protected long forwardBit;
protected long backwardBit;
protected long directionBitMask;
protected long roundaboutBit;
protected EncodedDoubleValue speedEncoder;
// bit to signal that way is accepted
protected long acceptBit;
protected long ferryBit;
protected PMap properties;
// This value determines the maximal possible speed of any road regardless the maxspeed value
// lower values allow more compact representation of the routing graph
protected int maxPossibleSpeed;
/* processing properties (to be initialized lazy when needed) */
protected EdgeExplorer edgeOutExplorer;
protected EdgeExplorer edgeInExplorer;
/* Edge Flag Encoder fields */
private long nodeBitMask;
private long wayBitMask;
private long relBitMask;
private EncodedValue turnCostEncoder;
private long turnRestrictionBit;
private boolean blockByDefault = true;
private boolean blockFords = true;
private boolean registered;
private ConditionalTagInspector conditionalTagInspector;
public AbstractFlagEncoder(PMap properties) {
throw new RuntimeException("This method must be overridden in derived classes");
}
public AbstractFlagEncoder(String propertiesStr) {
this(new PMap(propertiesStr));
}
/**
* @param speedBits specify the number of bits used for speed
* @param speedFactor specify the factor to multiple the stored value (can be used to increase
* or decrease accuracy of speed value)
* @param maxTurnCosts specify the maximum value used for turn costs, if this value is reached a
* turn is forbidden and results in costs of positive infinity.
*/
protected AbstractFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) {
this.maxTurnCosts = maxTurnCosts <= 0 ? 0 : maxTurnCosts;
this.speedBits = speedBits;
this.speedFactor = speedFactor;
oneways.add("yes");
oneways.add("true");
oneways.add("1");
oneways.add("-1");
ferries.add("shuttle_train");
ferries.add("ferry");
}
// should be called as last method in constructor, move out of the flag encoder somehow
protected void init() {
// we should move 'OSM to object' logic into the DataReader like OSMReader, but this is a major task as we need to convert OSM format into kind of a standard/generic format
conditionalTagInspector = new ConditionalOSMTagInspector(DateRangeParser.createCalendar(), restrictions, restrictedValues, intendedValues);
}
@Override
public boolean isRegistered() {
return registered;
}
public void setRegistered(boolean registered) {
this.registered = registered;
}
/**
* Should potential barriers block when no access limits are given?
*/
public void setBlockByDefault(boolean blockByDefault) {
this.blockByDefault = blockByDefault;
}
public boolean isBlockFords() {
return blockFords;
}
public void setBlockFords(boolean blockFords) {
this.blockFords = blockFords;
}
public ConditionalTagInspector getConditionalTagInspector() {
return conditionalTagInspector;
}
protected void setConditionalTagInspector(ConditionalTagInspector conditionalTagInspector) {
this.conditionalTagInspector = conditionalTagInspector;
}
/**
* Defines the bits for the node flags, which are currently used for barriers only.
* <p>
*
* @return incremented shift value pointing behind the last used bit
*/
public int defineNodeBits(int index, int shift) {
return shift;
}
/**
* Defines bits used for edge flags used for access, speed etc.
* <p>
*
* @param shift bit offset for the first bit used by this encoder
* @return incremented shift value pointing behind the last used bit
*/
public int defineWayBits(int index, int shift) {
// define the first 2 speedBits in flags for routing
forwardBit = 1L << shift;
backwardBit = 2L << shift;
directionBitMask = 3L << shift;
shift += 2;
roundaboutBit = 1L << shift;
shift++;
// define internal flags for parsing
index *= 2;
acceptBit = 1L << index;
ferryBit = 2L << index;
return shift;
}
/**
* Defines the bits which are used for relation flags.
* <p>
*
* @return incremented shift value pointing behind the last used bit
*/
public int defineRelationBits(int index, int shift) {
return shift;
}
/**
* Analyze the properties of a relation and create the routing flags for the second read step.
* In the pre-parsing step this method will be called to determine the useful relation tags.
* <p>
*/
public abstract long handleRelationTags(ReaderRelation relation, long oldRelationFlags);
/**
* Decide whether a way is routable for a given mode of travel. This skips some ways before
* handleWayTags is called.
* <p>
*
* @return the encoded value to indicate if this encoder allows travel or not.
*/
public abstract long acceptWay(ReaderWay way);
/**
* Analyze properties of a way and create the routing flags. This method is called in the second
* parsing step.
*/
public abstract long handleWayTags(ReaderWay way, long allowed, long relationFlags);
/**
* Parse tags on nodes. Node tags can add to speed (like traffic_signals) where the value is
* strict negative or blocks access (like a barrier), then the value is strict positive.This
* method is called in the second parsing step.
*/
public long handleNodeTags(ReaderNode node) {
// absolute barriers always block
if (node.hasTag("barrier", absoluteBarriers))
return directionBitMask;
// movable barriers block if they are not marked as passable
if (node.hasTag("barrier", potentialBarriers)) {
boolean locked = false;
if (node.hasTag("locked", "yes"))
locked = true;
for (String res : restrictions) {
if (!locked && node.hasTag(res, intendedValues))
return 0;
if (node.hasTag(res, restrictedValues))
return directionBitMask;
}
if (blockByDefault)
return directionBitMask;
}
// In case explicit flag ford=no, don't block
if (blockFords
&& (node.hasTag("highway", "ford") || node.hasTag("ford"))
&& !node.hasTag(restrictions, intendedValues)
&& !node.hasTag("ford", "no")) {
return directionBitMask;
}
return 0;
}
@Override
public InstructionAnnotation getAnnotation(long flags, Translation tr) {
return InstructionAnnotation.EMPTY;
}
/**
* Swapping directions means swapping bits which are dependent on the direction of an edge like
* the access bits. But also direction dependent speed values should be swapped too. Keep in
* mind that this method is performance critical!
*/
public long reverseFlags(long flags) {
long dir = flags & directionBitMask;
if (dir == directionBitMask || dir == 0)
return flags;
return flags ^ directionBitMask;
}
/**
* Sets default flags with specified access.
*/
public long flagsDefault(boolean forward, boolean backward) {
long flags = speedEncoder.setDefaultValue(0);
return setAccess(flags, forward, backward);
}
@Override
public long setAccess(long flags, boolean forward, boolean backward) {
return setBool(setBool(flags, K_BACKWARD, backward), K_FORWARD, forward);
}
@Override
public long setSpeed(long flags, double speed) {
if (speed < 0 || Double.isNaN(speed))
throw new IllegalArgumentException("Speed cannot be negative or NaN: " + speed
+ ", flags:" + BitUtil.LITTLE.toBitString(flags));
if (speed < speedEncoder.factor / 2)
return setLowSpeed(flags, speed, false);
if (speed > getMaxSpeed())
speed = getMaxSpeed();
return speedEncoder.setDoubleValue(flags, speed);
}
protected long setLowSpeed(long flags, double speed, boolean reverse) {
return setAccess(speedEncoder.setDoubleValue(flags, 0), false, false);
}
@Override
public double getSpeed(long flags) {
double speedVal = speedEncoder.getDoubleValue(flags);
if (speedVal < 0)
throw new IllegalStateException("Speed was negative!? " + speedVal);
return speedVal;
}
@Override
public long setReverseSpeed(long flags, double speed) {
return setSpeed(flags, speed);
}
@Override
public double getReverseSpeed(long flags) {
return getSpeed(flags);
}
@Override
public long setProperties(double speed, boolean forward, boolean backward) {
return setAccess(setSpeed(0, speed), forward, backward);
}
@Override
public double getMaxSpeed() {
return speedEncoder.getMaxValue();
}
/**
* @return -1 if no maxspeed found
*/
protected double getMaxSpeed(ReaderWay way) {
double maxSpeed = parseSpeed(way.getTag("maxspeed"));
double fwdSpeed = parseSpeed(way.getTag("maxspeed:forward"));
if (fwdSpeed >= 0 && (maxSpeed < 0 || fwdSpeed < maxSpeed))
maxSpeed = fwdSpeed;
double backSpeed = parseSpeed(way.getTag("maxspeed:backward"));
if (backSpeed >= 0 && (maxSpeed < 0 || backSpeed < maxSpeed))
maxSpeed = backSpeed;
return maxSpeed;
}
@Override
public int hashCode() {
int hash = 7;
hash = 61 * hash + (int) this.directionBitMask;
hash = 61 * hash + this.toString().hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
// only rely on the string
// if (getClass() != obj.getClass())
// return false;
final AbstractFlagEncoder other = (AbstractFlagEncoder) obj;
if (this.directionBitMask != other.directionBitMask)
return false;
return this.toString().equals(other.toString());
}
/**
* @return the speed in km/h
*/
protected double parseSpeed(String str) {
if (Helper.isEmpty(str))
return -1;
// on some German autobahns and a very few other places
if ("none".equals(str))
return 140;
if (str.endsWith(":rural") || str.endsWith(":trunk"))
return 80;
if (str.endsWith(":urban"))
return 50;
if (str.equals("walk") || str.endsWith(":living_street"))
return 6;
try {
int val;
// see https://en.wikipedia.org/wiki/Knot_%28unit%29#Definitions
int mpInteger = str.indexOf("mp");
if (mpInteger > 0) {
str = str.substring(0, mpInteger).trim();
val = Integer.parseInt(str);
return val * DistanceCalcEarth.KM_MILE;
}
int knotInteger = str.indexOf("knots");
if (knotInteger > 0) {
str = str.substring(0, knotInteger).trim();
val = Integer.parseInt(str);
return val * 1.852;
}
int kmInteger = str.indexOf("km");
if (kmInteger > 0) {
str = str.substring(0, kmInteger).trim();
} else {
kmInteger = str.indexOf("kph");
if (kmInteger > 0) {
str = str.substring(0, kmInteger).trim();
}
}
return Integer.parseInt(str);
} catch (Exception ex) {
return -1;
}
}
/**
* Second parsing step. Invoked after splitting the edges. Currently used to offer a hook to
* calculate precise speed values based on elevation data stored in the specified edge.
*/
public void applyWayTags(ReaderWay way, EdgeIteratorState edge) {
}
/**
* Special handling for ferry ways.
*/
protected double getFerrySpeed(ReaderWay way, double unknownSpeed, double shortTripsSpeed, double longTripsSpeed) {
long duration = 0;
try {
// During the reader process we have converted the duration value into a artificial tag called "duration:seconds".
duration = Long.parseLong(way.getTag("duration:seconds"));
} catch (Exception ex) {
}
// seconds to hours
double durationInHours = duration / 60d / 60d;
if (durationInHours > 0)
try {
// Check if our graphhopper specific artificially created estimated_distance way tag is present
Number estimatedLength = way.getTag("estimated_distance", null);
if (estimatedLength != null) {
// to km
double val = estimatedLength.doubleValue() / 1000;
// If duration AND distance is available we can calculate the speed more precisely
// and set both speed to the same value. Factor 1.4 slower because of waiting time!
double calculatedTripSpeed = val / durationInHours / 1.4;
// Plausibility check especially for the case of wrongly used PxM format with the intention to
// specify the duration in minutes, but actually using months
if (calculatedTripSpeed > 0.01d) {
// If we have a very short ferry with an average lower compared to what we can encode
// then we need to avoid setting it as otherwise the edge would not be found at all any more.
if (Math.round(calculatedTripSpeed) > speedEncoder.factor / 2) {
shortTripsSpeed = Math.round(calculatedTripSpeed);
if (shortTripsSpeed > getMaxSpeed())
shortTripsSpeed = getMaxSpeed();
longTripsSpeed = shortTripsSpeed;
} else {
// Now we set to the lowest possible still accessible speed.
shortTripsSpeed = speedEncoder.factor / 2;
}
} else {
long lastId = way.getNodes().isEmpty() ? -1 : way.getNodes().get(way.getNodes().size() - 1);
long firstId = way.getNodes().isEmpty() ? -1 : way.getNodes().get(0);
if (firstId != lastId)
logger.warn("Unrealistic long duration ignored in way with way ID=" + way.getId() + " : Duration tag value="
+ way.getTag("duration") + " (=" + Math.round(duration / 60d) + " minutes)");
durationInHours = 0;
}
}
} catch (Exception ex) {
}
if (durationInHours == 0) {
// unknown speed -> put penalty on ferry transport
return unknownSpeed;
} else if (durationInHours > 1) {
// lengthy ferries should be faster than short trip ferry
return longTripsSpeed;
} else {
return shortTripsSpeed;
}
}
void setWayBitMask(int usedBits, int shift) {
wayBitMask = (1L << usedBits) - 1;
wayBitMask <<= shift;
}
long getWayBitMask() {
return wayBitMask;
}
void setRelBitMask(int usedBits, int shift) {
relBitMask = (1L << usedBits) - 1;
relBitMask <<= shift;
}
long getRelBitMask() {
return relBitMask;
}
void setNodeBitMask(int usedBits, int shift) {
nodeBitMask = (1L << usedBits) - 1;
nodeBitMask <<= shift;
}
long getNodeBitMask() {
return nodeBitMask;
}
/**
* Defines the bits reserved for storing turn restriction and turn cost
* <p>
*
* @param shift bit offset for the first bit used by this encoder
* @return incremented shift value pointing behind the last used bit
*/
public int defineTurnBits(int index, int shift) {
if (maxTurnCosts == 0)
return shift;
// optimization for turn restrictions only
else if (maxTurnCosts == 1) {
turnRestrictionBit = 1L << shift;
return shift + 1;
}
int turnBits = Helper.countBitValue(maxTurnCosts);
turnCostEncoder = new EncodedValue("TurnCost", shift, turnBits, 1, 0, maxTurnCosts) {
// override to avoid expensive Math.round
@Override
public final long getValue(long flags) {
// find value
flags &= mask;
flags >>>= shift;
return flags;
}
};
return shift + turnBits;
}
@Override
public boolean isTurnRestricted(long flags) {
if (maxTurnCosts == 0)
return false;
else if (maxTurnCosts == 1)
return (flags & turnRestrictionBit) != 0;
return turnCostEncoder.getValue(flags) == maxTurnCosts;
}
@Override
public double getTurnCost(long flags) {
if (maxTurnCosts == 0)
return 0;
else if (maxTurnCosts == 1)
return ((flags & turnRestrictionBit) == 0) ? 0 : Double.POSITIVE_INFINITY;
long cost = turnCostEncoder.getValue(flags);
if (cost == maxTurnCosts)
return Double.POSITIVE_INFINITY;
return cost;
}
@Override
public long getTurnFlags(boolean restricted, double costs) {
if (maxTurnCosts == 0)
return 0;
else if (maxTurnCosts == 1) {
if (costs != 0)
throw new IllegalArgumentException("Only restrictions are supported");
return restricted ? turnRestrictionBit : 0;
}
if (restricted) {
if (costs != 0 || Double.isInfinite(costs))
throw new IllegalArgumentException("Restricted turn can only have infinite costs (or use 0)");
} else if (costs >= maxTurnCosts)
throw new IllegalArgumentException("Cost is too high. Or specifiy restricted == true");
if (costs < 0)
throw new IllegalArgumentException("Turn costs cannot be negative");
if (costs >= maxTurnCosts || restricted)
costs = maxTurnCosts;
return turnCostEncoder.setValue(0L, (int) costs);
}
protected boolean isFerry(long internalFlags) {
return (internalFlags & ferryBit) != 0;
}
protected boolean isAccept(long internalFlags) {
return (internalFlags & acceptBit) != 0;
}
@Override
public boolean isBackward(long flags) {
return (flags & backwardBit) != 0;
}
@Override
public boolean isForward(long flags) {
return (flags & forwardBit) != 0;
}
@Override
public long setBool(long flags, int key, boolean value) {
switch (key) {
case K_FORWARD:
return value ? flags | forwardBit : flags & ~forwardBit;
case K_BACKWARD:
return value ? flags | backwardBit : flags & ~backwardBit;
case K_ROUNDABOUT:
return value ? flags | roundaboutBit : flags & ~roundaboutBit;
default:
throw new IllegalArgumentException("Unknown key " + key + " for boolean value");
}
}
@Override
public boolean isBool(long flags, int key) {
switch (key) {
case K_FORWARD:
return isForward(flags);
case K_BACKWARD:
return isBackward(flags);
case K_ROUNDABOUT:
return (flags & roundaboutBit) != 0;
default:
throw new IllegalArgumentException("Unknown key " + key + " for boolean value");
}
}
@Override
public long setLong(long flags, int key, long value) {
throw new UnsupportedOperationException("Unknown key " + key + " for long value.");
}
@Override
public long getLong(long flags, int key) {
throw new UnsupportedOperationException("Unknown key " + key + " for long value.");
}
@Override
public long setDouble(long flags, int key, double value) {
throw new UnsupportedOperationException("Unknown key " + key + " for double value.");
}
@Override
public double getDouble(long flags, int key) {
throw new UnsupportedOperationException("Unknown key " + key + " for double value.");
}
/**
* @param way: needed to retrieve tags
* @param speed: speed guessed e.g. from the road type or other tags
* @return The assumed speed.
*/
protected double applyMaxSpeed(ReaderWay way, double speed) {
double maxSpeed = getMaxSpeed(way);
// We obay speed limits
if (maxSpeed >= 0) {
// We assume that the average speed is 90% of the allowed maximum
return maxSpeed * 0.9;
}
return speed;
}
protected String getPropertiesString() {
return "speed_factor=" + speedFactor + "|speed_bits=" + speedBits + "|turn_costs=" + (maxTurnCosts > 0);
}
@Override
public boolean supports(Class<?> feature) {
if (TurnWeighting.class.isAssignableFrom(feature))
return maxTurnCosts > 0;
return false;
}
}