VehicleFlagEncoder.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.BooleanEncodedValue;
import com.graphhopper.routing.ev.EncodedValue;
import com.graphhopper.routing.ev.SimpleBooleanEncodedValue;
import com.graphhopper.routing.ev.UnsignedDecimalEncodedValue;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.storage.ConditionalEdges;
import com.graphhopper.storage.IntsRef;
import com.graphhopper.util.BitUtil;
import com.graphhopper.util.Helper;
import com.graphhopper.util.PMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.graphhopper.routing.util.EncodingManager.getKey;
public abstract class VehicleFlagEncoder extends ORSAbstractFlagEncoder {
public static final String KEY_ESTIMATED_DISTANCE = "estimated_distance";
public static final String KEY_HIGHWAY = "highway";
public static final String KEY_ONEWAY = "oneway";
public static final String KEY_VEHICLE_FORWARD = "vehicle:forward";
public static final String KEY_MOTOR_VEHICLE_FORWARD = "motor_vehicle:forward";
public static final String KEY_MOTORROAD = "motorroad";
private static final double ACCELERATION_SPEED_CUTOFF_MAX = 80.0;
private static final double ACCELERATION_SPEED_CUTOFF_MIN = 20.0;
public static final int AVERAGE_SECS_TO_100_KMPH = 10;
public static final String KEY_MOTORWAY_LINK = "motorway_link";
public static final String KEY_RESIDENTIAL = "residential";
protected SpeedLimitHandler speedLimitHandler;
private double accelerationModifier = 0.0;
protected boolean speedTwoDirections = false;
protected int maxTrackGradeLevel = 3;
// Take into account acceleration calculations when determining travel speed
protected boolean useAcceleration = false;
// This value determines the maximal possible on roads with bad surfaces
protected int badSurfaceSpeed;
protected final double minPossibleSpeed;
protected Map<String, Integer> trackTypeSpeedMap;
protected Map<String, Integer> badSurfaceSpeedMap;
protected Map<String, Integer> defaultSpeedMap;
private boolean hasConditionalAccess;
private boolean hasConditionalSpeed;
private BooleanEncodedValue conditionalAccessEncoder;
private BooleanEncodedValue conditionalSpeedEncoder;
protected void setProperties(PMap properties) {
hasConditionalAccess = properties.getBool(ConditionalEdges.ACCESS, false);
hasConditionalSpeed = properties.getBool(ConditionalEdges.SPEED, false);
this.blockFords(properties.getBool("block_fords", true));
this.blockBarriers(properties.getBool("block_barriers", true));
speedTwoDirections = properties.getBool("speed_two_directions", true);
useAcceleration = properties.getBool("use_acceleration", false);
maxTrackGradeLevel = properties.getInt("maximum_grade_level", maxTrackGradeLevel);
}
VehicleFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) {
super(speedBits, speedFactor, maxTurnCosts);
minPossibleSpeed = this.speedFactor;
restrictions.addAll(Arrays.asList("motorcar", "motor_vehicle", "vehicle", "access"));
restrictedValues.add("private");
restrictedValues.add("no");
restrictedValues.add("restricted");
restrictedValues.add("military");
intendedValues.add("yes");
intendedValues.add("permissive");
intendedValues.add("destination"); // This is needed to allow the passing of barriers that are marked as destination
passByDefaultBarriers.add("gate");
passByDefaultBarriers.add("lift_gate");
passByDefaultBarriers.add("kissing_gate");
passByDefaultBarriers.add("swing_gate");
blockByDefaultBarriers.add("bollard");
blockByDefaultBarriers.add("stile");
blockByDefaultBarriers.add("turnstile");
blockByDefaultBarriers.add("cycle_barrier");
blockByDefaultBarriers.add("motorcycle_barrier");
blockByDefaultBarriers.add("block");
trackTypeSpeedMap = new HashMap<>();
trackTypeSpeedMap.put("grade1", 40); // paved
trackTypeSpeedMap.put("grade2", 30); // now unpaved - gravel mixed with ...
trackTypeSpeedMap.put("grade3", 20); // ... hard and soft materials
trackTypeSpeedMap.put("grade4", 15);
trackTypeSpeedMap.put("grade5", 10);
badSurfaceSpeedMap = new HashMap<>();
badSurfaceSpeedMap.put("asphalt", -1);
badSurfaceSpeedMap.put("concrete", -1);
badSurfaceSpeedMap.put("concrete:plates", -1);
badSurfaceSpeedMap.put("concrete:lanes", -1);
badSurfaceSpeedMap.put("paved", -1);
badSurfaceSpeedMap.put("cement", 80);
badSurfaceSpeedMap.put("compacted", 80);
badSurfaceSpeedMap.put("fine_gravel", 60);
badSurfaceSpeedMap.put("paving_stones", 40);
badSurfaceSpeedMap.put("metal", 40);
badSurfaceSpeedMap.put("bricks", 40);
badSurfaceSpeedMap.put("grass", 30);
badSurfaceSpeedMap.put("wood", 30);
badSurfaceSpeedMap.put("sett", 30);
badSurfaceSpeedMap.put("grass_paver", 30);
badSurfaceSpeedMap.put("gravel", 30);
badSurfaceSpeedMap.put("unpaved", 30);
badSurfaceSpeedMap.put("ground", 30);
badSurfaceSpeedMap.put("dirt", 30);
badSurfaceSpeedMap.put("pebblestone", 30);
badSurfaceSpeedMap.put("tartan", 30);
badSurfaceSpeedMap.put("cobblestone", 20);
badSurfaceSpeedMap.put("clay", 20);
badSurfaceSpeedMap.put("earth", 15);
badSurfaceSpeedMap.put("stone", 15);
badSurfaceSpeedMap.put("rocky", 15);
badSurfaceSpeedMap.put("sand", 15);
badSurfaceSpeedMap.put("mud", 10);
badSurfaceSpeedMap.put("unknown", 30);
// limit speed on bad surfaces to 30 km/h
badSurfaceSpeed = 30;
maxPossibleSpeed = 140;
defaultSpeedMap = new HashMap<>();
// autobahn
defaultSpeedMap.put("motorway", 100);
defaultSpeedMap.put(KEY_MOTORWAY_LINK, 60);
defaultSpeedMap.put(KEY_MOTORROAD, 90);
// bundesstraße
defaultSpeedMap.put("trunk", 85);
defaultSpeedMap.put("trunk_link", 60);
// linking bigger town
defaultSpeedMap.put("primary", 65);
defaultSpeedMap.put("primary_link", 50);
// linking towns + villages
defaultSpeedMap.put("secondary", 60);
defaultSpeedMap.put("secondary_link", 50);
// streets without middle line separation
defaultSpeedMap.put("tertiary", 50);
defaultSpeedMap.put("tertiary_link", 40);
defaultSpeedMap.put("unclassified", 30);
defaultSpeedMap.put(KEY_RESIDENTIAL, 30);
// spielstraße
defaultSpeedMap.put("living_street", 10);
defaultSpeedMap.put("service", 20);
// unknown road
defaultSpeedMap.put("road", 20);
// forestry stuff
defaultSpeedMap.put("track", 15);
}
@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);
avgSpeedEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "average_speed"), speedBits, speedFactor, speedTwoDirections);
registerNewEncodedValue.add(avgSpeedEnc);
if (hasConditionalAccess)
registerNewEncodedValue.add(conditionalAccessEncoder = new SimpleBooleanEncodedValue(EncodingManager.getKey(prefix, ConditionalEdges.ACCESS), true));
if (hasConditionalSpeed)
registerNewEncodedValue.add(conditionalSpeedEncoder = new SimpleBooleanEncodedValue(EncodingManager.getKey(prefix, ConditionalEdges.SPEED), false));
}
@Override
public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.Access access) {
return handleWayTags(edgeFlags, way, access, 0);
}
public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.Access access, long relationFlags) {
if (access.canSkip())
return edgeFlags;
if (!access.isFerry()) {
// get assumed speed from highway type
double speed = getSpeed(way);
speed = applyMaxSpeed(way, speed);
// TODO: save conditional speeds only if their value is different from the default speed
if (getConditionalSpeedInspector() != null && getConditionalSpeedInspector().hasConditionalSpeed(way))
if (getConditionalSpeedInspector().hasLazyEvaluatedConditions() && conditionalSpeedEncoder != null) {
conditionalSpeedEncoder.setBool(false, edgeFlags, true);
} else {
// conditional maxspeed overrides unconditional one
speed = applyConditionalSpeed(getConditionalSpeedInspector().getTagValue(), speed);
}
speed = getSurfaceSpeed(way, speed);
if (way.hasTag(KEY_ESTIMATED_DISTANCE)) {
if (way.hasTag(KEY_HIGHWAY, KEY_RESIDENTIAL)) {
speed = addResedentialPenalty(speed, way);
} else if (this.useAcceleration) {
double estDist = way.getTag(KEY_ESTIMATED_DISTANCE, Double.MAX_VALUE);
speed = adjustSpeedForAcceleration(estDist, speed);
}
}
boolean isRoundabout = way.hasTag("junction", "roundabout");
if (isRoundabout) { // Runge
roundaboutEnc.setBool(false, edgeFlags, true);
//http://www.sidrasolutions.com/Documents/OArndt_Speed%20Control%20at%20Roundabouts_23rdARRBConf.pdf
if (way.hasTag(KEY_HIGHWAY, "mini_roundabout"))
speed = speed < 25 ? speed : 25;
if (way.hasTag("lanes")) {
try {
// The following line throws exceptions when it tries to parse a value "3; 2"
int lanes = Integer.parseInt(way.getTag("lanes"));
if (lanes >= 2)
speed = speed < 40 ? speed : 40;
else
speed = speed < 35 ? speed : 35;
} catch (Exception ex) {
// do nothing
}
}
}
setSpeed(false, edgeFlags, speed);
setSpeed(true, edgeFlags, speed);
if (isOneway(way) || isRoundabout) {
if (isForwardOneway(way))
setAccess(access, edgeFlags, true, false);
if (isBackwardOneway(way))
setAccess(access, edgeFlags, false, true);
} else {
setAccess(access, edgeFlags, true, true);
}
if (access.isConditional() && conditionalAccessEncoder != null)
conditionalAccessEncoder.setBool(false, edgeFlags, true);
} else {
double ferrySpeed = ferrySpeedCalc.getSpeed(way);
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
setSpeed(false, edgeFlags, ferrySpeed);
setSpeed(true, edgeFlags, ferrySpeed);
}
return edgeFlags;
}
// Override this method in order to set minimum speed rather than disabling access
@Override
protected void setSpeed(boolean reverse, IntsRef edgeFlags, double speed) {
if (speed >= 0.0D && !Double.isNaN(speed)) {
if (speed < minPossibleSpeed)
speed = minPossibleSpeed;
else if (speed > this.getMaxSpeed())
speed = this.getMaxSpeed();
this.avgSpeedEnc.setDecimal(reverse, edgeFlags, speed);
} else {
throw new IllegalArgumentException("Speed cannot be negative or NaN: " + speed + ", flags:" + BitUtil.LITTLE.toBitString(edgeFlags));
}
}
private void setAccess(EncodingManager.Access access, IntsRef edgeFlags, boolean fwd, boolean bwd) {
if (fwd)
accessEnc.setBool(false, edgeFlags, true);
if (bwd)
accessEnc.setBool(true, edgeFlags, true);
if (access.isConditional() && conditionalAccessEncoder != null) {
if (fwd)
conditionalAccessEncoder.setBool(false, edgeFlags, true);
if (bwd)
conditionalAccessEncoder.setBool(true, edgeFlags, true);
}
}
/**
* make sure that isOneway is called before
*/
protected boolean isBackwardOneway(ReaderWay way) {
return way.hasTag(KEY_ONEWAY, "-1")
|| way.hasTag(KEY_VEHICLE_FORWARD, "no")
|| way.hasTag(KEY_MOTOR_VEHICLE_FORWARD, "no");
}
/**
* make sure that isOneway is called before
*/
protected boolean isForwardOneway(ReaderWay way) {
return !way.hasTag(KEY_ONEWAY, "-1")
&& !way.hasTag(KEY_VEHICLE_FORWARD, "no")
&& !way.hasTag(KEY_MOTOR_VEHICLE_FORWARD, "no");
}
protected boolean isOneway(ReaderWay way) {
return way.hasTag(KEY_ONEWAY, oneways)
|| way.hasTag("vehicle:backward")
|| way.hasTag(KEY_VEHICLE_FORWARD)
|| way.hasTag("motor_vehicle:backward")
|| way.hasTag(KEY_MOTOR_VEHICLE_FORWARD);
}
protected double getSpeed(ReaderWay way) {
String highwayValue = getHighway(way);
Integer speed = speedLimitHandler.getSpeed(highwayValue);
// Note that Math.round(NaN) == 0
int maxSpeed = (int) Math.round(getMaxSpeed(way));
if (maxSpeed <= 0)
maxSpeed = speedLimitHandler.getMaxSpeed(way);
if (maxSpeed > 0)
speed = maxSpeed;
if (speed == null)
throw new IllegalStateException(this + ", no speed found for: " + highwayValue + ", tags: " + way);
if (highwayValue.equals("track")) {
String tt = way.getTag("tracktype");
if (!Helper.isEmpty(tt)) {
Integer tInt = speedLimitHandler.getTrackTypeSpeed(tt);
if (tInt != null && tInt != -1)
speed = tInt;
}
}
return speed;
}
protected String getHighway(ReaderWay way) {
String highwayValue = way.getTag(KEY_HIGHWAY);
if (!Helper.isEmpty(highwayValue) && way.hasTag(KEY_MOTORROAD, "yes")
&& !highwayValue.equals("motorway") && !highwayValue.equals(KEY_MOTORWAY_LINK)) {
highwayValue = KEY_MOTORROAD;
}
return highwayValue;
}
@Override
protected double applyMaxSpeed(ReaderWay way, double speed) {
double maxSpeed = this.getMaxSpeed(way);
return isValidSpeed(maxSpeed) ? maxSpeed * 0.9D : speed;
}
/**
* @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 getSurfaceSpeed(ReaderWay way, double speed) {
// limit speed if bad surface
String surface = way.getTag("surface");
if (surface != null) {
Integer surfaceSpeed = speedLimitHandler.getSurfaceSpeed(surface);
if (speed > surfaceSpeed && surfaceSpeed != -1)
return surfaceSpeed;
}
return speed;
}
public String getWayInfo(ReaderWay way) {
StringBuilder str = new StringBuilder();
String highwayValue = way.getTag(KEY_HIGHWAY);
// for now only motorway links
if (KEY_MOTORWAY_LINK.equals(highwayValue)) {
String destination = way.getTag("destination");
if (!Helper.isEmpty(destination)) {
int counter = 0;
for (String d : destination.split(";")) {
if (d.trim().isEmpty())
continue;
if (counter > 0)
str.append(", ");
str.append(d.trim());
counter++;
}
}
}
if (str.length() == 0)
return str.toString();
// I18N
if (str.toString().contains(","))
return "destinations: " + str;
else
return "destination: " + str;
}
protected int getTrackGradeLevel(String grade) {
if (grade == null)
return 0;
if (grade.contains(";")) {
int maxGrade = 0;
try {
String[] values = grade.split(";");
for (String v : values) {
int iv = Integer.parseInt(v.replace("grade", "").trim());
if (iv > maxGrade)
maxGrade = iv;
}
return maxGrade;
} catch (Exception ex) {
// do nothing
}
}
switch (grade) {
case "grade":
case "grade1":
return 1;
case "grade2":
return 2;
case "grade3":
return 3;
case "grade4":
return 4;
case "grade5":
return 5;
case "grade6":
return 6;
default:
}
return 10;
}
double addResedentialPenalty(double baseSpeed, ReaderWay way) {
if (baseSpeed == 0)
return 0;
double speed = baseSpeed;
if (way.hasTag(KEY_HIGHWAY, KEY_RESIDENTIAL)) {
double estDist = way.getTag(KEY_ESTIMATED_DISTANCE, Double.MAX_VALUE);
// take into account number of nodes to get an average distance between nodes
double interimDistance = estDist;
int interimNodes = way.getNodes().size() - 2;
if (interimNodes > 0) {
interimDistance = estDist / (interimNodes + 1);
}
if (interimDistance < 100) {
speed = speed * 0.5;
}
}
return speed;
}
/**
* Returns how many seconds it is assumed that this vehicle would reach 100 km/h taking into acocunt the acceleration modifier
*
* @return
*/
double secondsTo100KmpH() {
return AVERAGE_SECS_TO_100_KMPH + (accelerationModifier * AVERAGE_SECS_TO_100_KMPH);
}
/**
* Returns the acceleration in KM per hour per second.
*
* @return
*/
double accelerationKmpHpS() {
return 100.0 / secondsTo100KmpH();
}
/**
* Adjust the maximum speed taking into account supposed acceleration on the segment. The method looks at acceleration
* along the way (assuming starting from 0km/h) and then uses the length to travel and the supposed maximum speed
* to determine an average speed for travelling along the whole segment.
*
* @param distance How long the segment to travel along is
* @param maximumSpeedInKmph The maximum speed that a vehicle can travel along this segment (usually the speed limit)
* @return
*/
public double adjustSpeedForAcceleration(double distance, double maximumSpeedInKmph) {
// We only want to perform the adjustment if the road is a slower speed - main roads shouldnt be affected as much due to less junctions and changes in direction
if (maximumSpeedInKmph < ACCELERATION_SPEED_CUTOFF_MAX) {
if (distance <= 0) {
return maximumSpeedInKmph;
}
// slower roads can be assumed to have slower acceleration...
double normalisedSpeed = maximumSpeedInKmph;
if (normalisedSpeed < ACCELERATION_SPEED_CUTOFF_MIN)
normalisedSpeed = ACCELERATION_SPEED_CUTOFF_MIN;
normalisedSpeed = (normalisedSpeed - ACCELERATION_SPEED_CUTOFF_MIN) / (ACCELERATION_SPEED_CUTOFF_MAX - ACCELERATION_SPEED_CUTOFF_MIN);
accelerationModifier = Math.pow(0.01, normalisedSpeed);
double timeToMaxSpeed = durationToMaxSpeed(0, maximumSpeedInKmph);
// We need to calculate how much distance is travelled in acceleration/deceleration phases
double accelerationDistance = distanceTravelledInDuration(0, maximumSpeedInKmph, timeToMaxSpeed);
double distanceAtMaxSpeed = distance - (accelerationDistance * 2); // Double the distance because of deceleration aswell
if (distanceAtMaxSpeed < 0) {
double duration = durationToTravelDistance(0, maximumSpeedInKmph, distance / 2);
if (duration == 0) {
duration = 1;
}
return convertMpsToKmph(distance / (duration * 2));
} else {
double timeAtMaxSpeed = distanceAtMaxSpeed / convertKmphToMps(maximumSpeedInKmph);
double averageSpeedMps = distance / (timeToMaxSpeed * 2 + timeAtMaxSpeed);
return convertMpsToKmph(averageSpeedMps);
}
} else {
return maximumSpeedInKmph;
}
}
/**
* How many seconds does it take to reach maximum speed based on initial speed and acceleration.
*
* @param initialSpeedInKmph How fast the vehicle is travelling at the start of the calculation
* @param maxSpeedInKmph The target speed to be reached
* @return How long it takes to reach the speed in seconds
*/
private double durationToMaxSpeed(double initialSpeedInKmph, double maxSpeedInKmph) {
return (maxSpeedInKmph - initialSpeedInKmph) / accelerationKmpHpS();
}
/**
* How long in seconds does it take to reach the intended distance based on the initial travelling speed and the
* maximum speed that can be travelled.
*
* @param initialSpeedInKmph The speed of the vehicle when starting the calculation
* @param maxSpeedInKmph The maximum speed the vehicle can travel at
* @param distanceInM The target distance to be travelled
* @return How long it takes in seconds to reach the target distance
*/
private double durationToTravelDistance(double initialSpeedInKmph, double maxSpeedInKmph, double distanceInM) {
double secondsTravelled = 0;
double distanceTravelled = 0;
double currentSpeed = initialSpeedInKmph;
while (currentSpeed < maxSpeedInKmph && distanceTravelled < distanceInM) {
currentSpeed += accelerationKmpHpS();
secondsTravelled += 1;
distanceTravelled += convertKmphToMps(currentSpeed);
}
double distanceRemaining = distanceInM - distanceTravelled;
if (distanceRemaining > 0) {
secondsTravelled += (distanceRemaining / convertKmphToMps(maxSpeedInKmph));
}
return secondsTravelled;
}
/**
* How far can the vehicle travel in the specified time frame
*
* @param initialSpeedInKmph The starting speed of the vehicle
* @param maxSpeedInKmph The maximum travel speed
* @param duration How long is the vehicle travelling for
* @return The distance in metres that the vehicle travels in the specified time
*/
private double distanceTravelledInDuration(double initialSpeedInKmph, double maxSpeedInKmph, double duration) {
double secondsTravelled = 0;
double distanceTravelled = 0;
double currentSpeed = initialSpeedInKmph;
while (currentSpeed < maxSpeedInKmph && secondsTravelled < duration) {
currentSpeed += accelerationKmpHpS();
secondsTravelled += 1;
distanceTravelled += convertKmphToMps(currentSpeed);
}
double secondsRemaining = duration - secondsTravelled;
if (secondsRemaining > 0) {
distanceTravelled += (secondsRemaining * convertKmphToMps(maxSpeedInKmph));
}
return distanceTravelled;
}
/**
* Convert kilometres per hour to metres per second
*
* @param speedInKmph The speed to be converted in km per hour
* @return The speed in metres per second
*/
private double convertKmphToMps(double speedInKmph) {
return (speedInKmph * 1000) / 3600;
}
/**
* Convert metres per second to kilometres per hour
*
* @param speedInMps The speed in metres per second
* @return The speed in kilometres per hour
*/
private double convertMpsToKmph(double speedInMps) {
return (3600 * speedInMps) / 1000;
}
protected void initSpeedLimitHandler(String profile) {
speedLimitHandler = new SpeedLimitHandler(profile, defaultSpeedMap, badSurfaceSpeedMap, trackTypeSpeedMap);
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final VehicleFlagEncoder other = (VehicleFlagEncoder) obj;
return toString().equals(other.toString());
}
@Override
public int hashCode() {
return ("VehicleFlagEncoder" + this).hashCode();
}
}