/*
* 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.ReaderWay;
import com.graphhopper.util.BitUtil;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.PMap;
import com.graphhopper.util.PointList;
import static com.graphhopper.util.Helper.keepIn;
/**
* Stores two speed values into an edge to support avoiding too much incline
* <p>
*
* @author Peter Karich
*/
public class Bike2WeightFlagEncoder extends BikeFlagEncoder {
private EncodedDoubleValue reverseSpeedEncoder;
public Bike2WeightFlagEncoder() {
super();
}
public Bike2WeightFlagEncoder(String propertiesStr) {
super(new PMap(propertiesStr));
}
public Bike2WeightFlagEncoder(PMap properties) {
super(properties);
}
public Bike2WeightFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) {
super(speedBits, speedFactor, maxTurnCosts);
}
@Override
public int getVersion() {
return 2;
}
@Override
public int defineWayBits(int index, int shift) {
shift = super.defineWayBits(index, shift);
reverseSpeedEncoder = new EncodedDoubleValue("Reverse Speed", shift, speedBits, speedFactor,
getHighwaySpeed("cycleway"), maxPossibleSpeed);
shift += reverseSpeedEncoder.getBits();
return shift;
}
@Override
public double getReverseSpeed(long flags) {
return reverseSpeedEncoder.getDoubleValue(flags);
}
@Override
public long setReverseSpeed(long flags, double speed) {
if (speed < 0)
throw new IllegalArgumentException("Speed cannot be negative: " + speed + ", flags:" + BitUtil.LITTLE.toBitString(flags));
if (speed < speedEncoder.factor / 2)
return setLowSpeed(flags, speed, true);
if (speed > getMaxSpeed())
speed = getMaxSpeed();
return reverseSpeedEncoder.setDoubleValue(flags, speed);
}
@Override
public long handleSpeed(ReaderWay way, double speed, long flags) {
// handle oneways
flags = super.handleSpeed(way, speed, flags);
if (isBackward(flags))
flags = setReverseSpeed(flags, speed);
if (isForward(flags))
flags = setSpeed(flags, speed);
return flags;
}
@Override
protected long setLowSpeed(long flags, double speed, boolean reverse) {
if (reverse)
return setBool(reverseSpeedEncoder.setDoubleValue(flags, 0), K_BACKWARD, false);
return setBool(speedEncoder.setDoubleValue(flags, 0), K_FORWARD, false);
}
@Override
public long flagsDefault(boolean forward, boolean backward) {
long flags = super.flagsDefault(forward, backward);
if (backward)
return reverseSpeedEncoder.setDefaultValue(flags);
return flags;
}
@Override
public long setProperties(double speed, boolean forward, boolean backward) {
long flags = super.setProperties(speed, forward, backward);
if (backward)
return setReverseSpeed(flags, speed);
return flags;
}
@Override
public long reverseFlags(long flags) {
// swap access
flags = super.reverseFlags(flags);
// swap speeds
double otherValue = reverseSpeedEncoder.getDoubleValue(flags);
flags = setReverseSpeed(flags, speedEncoder.getDoubleValue(flags));
return setSpeed(flags, otherValue);
}
@Override
public void applyWayTags(ReaderWay way, EdgeIteratorState edge) {
PointList pl = edge.fetchWayGeometry(3);
if (!pl.is3D())
throw new IllegalStateException("To support speed calculation based on elevation data it is necessary to enable import of it.");
long flags = edge.getFlags();
if (way.hasTag("tunnel", "yes") || way.hasTag("bridge", "yes") || way.hasTag("highway", "steps")) {
// do not change speed
// note: although tunnel can have a difference in elevation it is very unlikely that the elevation data is correct for a tunnel
} else {
// Decrease the speed for ele increase (incline), and decrease the speed for ele decrease (decline). The speed-decrease
// has to be bigger (compared to the speed-increase) for the same elevation difference to simulate loosing energy and avoiding hills.
// For the reverse speed this has to be the opposite but again keeping in mind that up+down difference.
double incEleSum = 0, incDist2DSum = 0;
double decEleSum = 0, decDist2DSum = 0;
// double prevLat = pl.getLatitude(0), prevLon = pl.getLongitude(0);
double prevEle = pl.getElevation(0);
double fullDist2D = edge.getDistance();
if (Double.isInfinite(fullDist2D))
throw new IllegalStateException("Infinite distance should not happen due to #435. way ID=" + way.getId());
// for short edges an incline makes no sense and for 0 distances could lead to NaN values for speed, see #432
if (fullDist2D < 1)
return;
double eleDelta = pl.getElevation(pl.size() - 1) - prevEle;
if (eleDelta > 0.1) {
incEleSum = eleDelta;
incDist2DSum = fullDist2D;
} else if (eleDelta < -0.1) {
decEleSum = -eleDelta;
decDist2DSum = fullDist2D;
}
// // get a more detailed elevation information, but due to bad SRTM data this does not make sense now.
// for (int i = 1; i < pl.size(); i++)
// {
// double lat = pl.getLatitude(i);
// double lon = pl.getLongitude(i);
// double ele = pl.getElevation(i);
// double eleDelta = ele - prevEle;
// double dist2D = distCalc.calcDist(prevLat, prevLon, lat, lon);
// if (eleDelta > 0.1)
// {
// incEleSum += eleDelta;
// incDist2DSum += dist2D;
// } else if (eleDelta < -0.1)
// {
// decEleSum += -eleDelta;
// decDist2DSum += dist2D;
// }
// fullDist2D += dist2D;
// prevLat = lat;
// prevLon = lon;
// prevEle = ele;
// }
// Calculate slop via tan(asin(height/distance)) but for rather smallish angles where we can assume tan a=a and sin a=a.
// Then calculate a factor which decreases or increases the speed.
// Do this via a simple quadratic equation where y(0)=1 and y(0.3)=1/4 for incline and y(0.3)=2 for decline
double fwdIncline = incDist2DSum > 1 ? incEleSum / incDist2DSum : 0;
double fwdDecline = decDist2DSum > 1 ? decEleSum / decDist2DSum : 0;
double restDist2D = fullDist2D - incDist2DSum - decDist2DSum;
double maxSpeed = getHighwaySpeed("cycleway");
if (isForward(flags)) {
// use weighted mean so that longer incline influences speed more than shorter
double speed = getSpeed(flags);
double fwdFaster = 1 + 2 * keepIn(fwdDecline, 0, 0.2);
fwdFaster = fwdFaster * fwdFaster;
double fwdSlower = 1 - 5 * keepIn(fwdIncline, 0, 0.2);
fwdSlower = fwdSlower * fwdSlower;
speed = speed * (fwdSlower * incDist2DSum + fwdFaster * decDist2DSum + 1 * restDist2D) / fullDist2D;
flags = this.setSpeed(flags, keepIn(speed, PUSHING_SECTION_SPEED / 2, maxSpeed));
}
if (isBackward(flags)) {
double speedReverse = getReverseSpeed(flags);
double bwFaster = 1 + 2 * keepIn(fwdIncline, 0, 0.2);
bwFaster = bwFaster * bwFaster;
double bwSlower = 1 - 5 * keepIn(fwdDecline, 0, 0.2);
bwSlower = bwSlower * bwSlower;
speedReverse = speedReverse * (bwFaster * incDist2DSum + bwSlower * decDist2DSum + 1 * restDist2D) / fullDist2D;
flags = this.setReverseSpeed(flags, keepIn(speedReverse, PUSHING_SECTION_SPEED / 2, maxSpeed));
}
}
edge.setFlags(flags);
}
@Override
public String toString() {
return "bike2";
}
}