SteepnessUtil.java
package org.heigit.ors.routing.util;
import com.graphhopper.util.DistanceCalc;
import com.graphhopper.util.PointList;
import java.util.List;
public class SteepnessUtil {
public static final double ELEVATION_THRESHOLD = 20;
private SteepnessUtil() {
throw new IllegalStateException("Utility class should not be instantiated.");
}
public static int getCategory(double value) {
//Keep in sync with documentation: steepness.md
if (Double.isNaN(value))
return 0;
double absValue = Math.abs(value);
int res = 0;
// 0%: A flat road
if (absValue < 1.0)
res = 0;
// 1-3%: Slightly uphill but not particularly challenging. A bit like riding into the wind.
else if (absValue >= 1 && absValue < 4)
res = 1;
// 4-6%: A manageable gradient that can cause fatigue over long periods.
else if (absValue >= 4 && absValue < 7)
res = 2;
// 7-9%: Starting to become uncomfortable for seasoned riders, and very challenging for new climbers.
else if (absValue >= 7 && absValue < 10)
res = 3;
// 10%-15%: A painful gradient, especially if maintained for any length of time
else if (absValue >= 10 && absValue < 16)
res = 4;
// 16%+: Very challenging for riders of all abilities. Maintaining this sort of incline for any length of time is very painful.
else if (absValue >= 16)
res = 5;
return res * ((value > 0) ? 1 : -1);
}
public static void computeRouteSplits(PointList points, boolean reverse, DistanceCalc dc, List<RouteSplit> splits) {
splits.clear();
if (points.size() == 0)
return;
int nPoints = points.size();
int i0 = reverse ? nPoints - 1 : 0;
double maxAltitude = Double.MIN_VALUE;
double minAltitude = Double.MAX_VALUE;
double prevMinAltitude;
double prevMaxAltitude;
int iStart = i0;
double splitLength = 0;
double cumElev = 0.0;
RouteSplit prevSplit = null;
int prevGC = 0;
int iPoints = 0;
double x0 = points.getLon(i0);
double y0 = points.getLat(i0);
double z0 = points.getEle(i0);
if (z0 > maxAltitude)
maxAltitude = z0;
if (z0 < minAltitude)
minAltitude = z0;
for (int j = 1; j < nPoints; j++) {
int jj = reverse ? (nPoints - 1 - j) : j;
double x1 = points.getLon(jj);
double y1 = points.getLat(jj);
double z1 = points.getEle(jj);
double elevDiff = z1 - z0;
double length = dc.calcDist(y0, x0, y1, x1);
cumElev += elevDiff;
splitLength += length;
prevMinAltitude = minAltitude;
prevMaxAltitude = maxAltitude;
if (z1 > maxAltitude)
maxAltitude = z1;
if (z1 < minAltitude)
minAltitude = z1;
if (maxAltitude - z1 > ELEVATION_THRESHOLD || z1 - minAltitude > ELEVATION_THRESHOLD) {
boolean bApply = true;
int elevSign = cumElev > 0 ? 1 : -1;
double gradient = elevSign * 100 * (prevMaxAltitude - prevMinAltitude) / splitLength;
if (Double.isNaN(gradient) || Math.abs(gradient) > 30) // possibly noise
gradient = 0.0;
if (prevGC != 0) {
double zn = Double.MIN_NORMAL;
if (!reverse) {
if (jj + 1 < nPoints)
zn = points.getEle(jj + 1);
} else {
if (jj - 1 >= 0)
zn = points.getEle(jj - 1);
}
if (zn != Double.MIN_VALUE) {
double elevGap = length / 30;
if (
(
elevSign > 0 && prevGC > 0 || prevGC < 0
)
&& Math.abs(zn - z1) < elevGap) {
bApply = false;
}
}
}
if (bApply) {
int iEnd = reverse ? (nPoints - iPoints - 1) : (iPoints - 1);
int gc = getCategory(gradient);
RouteSplit split = new RouteSplit();
split.value = gc;
split.gradient = gradient;
split.length = splitLength;
split.verticalClimb = prevMaxAltitude - prevMinAltitude;
if (reverse) {
split.start = iEnd;
split.end = iStart;
splits.add(0, split);
} else {
split.start = iStart;
split.end = iEnd;
splits.add(split);
}
prevGC = gc;
prevSplit = split;
iStart = iEnd;
minAltitude = Math.min(z0, z1);
maxAltitude = Math.max(z0, z1);
splitLength = 0.0;
cumElev = elevDiff;
}
}
x0 = x1;
y0 = y1;
z0 = z1;
iPoints++;
}
if (splitLength > 0) {
int iEnd = reverse ? (nPoints - iPoints - 1) : (iPoints - 1);
double elevDiff = maxAltitude - minAltitude;
if (splits.isEmpty() && splitLength < 50 && elevDiff < ELEVATION_THRESHOLD)
elevDiff = 0;
double gradient = (cumElev > 0 ? 1 : -1) * 100 * elevDiff / splitLength;
if (Math.abs(gradient) > 7 && maxAltitude < 100 && splitLength < 120)
gradient = 0.0; // noise
int gc = getCategory(gradient);
if (prevSplit != null && (prevSplit.value == gc || splitLength < 25)) {
prevSplit.end = iEnd;
} else {
RouteSplit lastSplit = new RouteSplit();
lastSplit.value = gc;
lastSplit.gradient = gradient;
lastSplit.length = splitLength;
lastSplit.verticalClimb = elevDiff;
if (reverse) {
lastSplit.start = iEnd;
lastSplit.end = iStart;
splits.add(0, lastSplit);
} else {
lastSplit.start = iStart;
lastSplit.end = iEnd;
splits.add(lastSplit);
}
}
}
}
}