OsmAnd/OsmAnd-java/src/main/java/net/osmand/router/RouteStatisticsHelper.java

420 lines
15 KiB
Java
Raw Normal View History

package net.osmand.router;
2019-03-01 12:35:59 +01:00
import java.util.ArrayList;
2019-07-08 01:22:41 +02:00
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
2019-03-01 12:35:59 +01:00
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
2019-07-08 00:13:37 +02:00
import net.osmand.binary.BinaryMapRouteReaderAdapter;
import net.osmand.binary.RouteDataObject;
import net.osmand.render.RenderingRuleSearchRequest;
import net.osmand.render.RenderingRulesStorage;
2019-07-07 18:30:01 +02:00
public class RouteStatisticsHelper {
public static final String UNDEFINED_ATTR = "undefined";
2019-07-07 23:13:36 +02:00
private static final double H_STEP = 5;
private static final double H_SLOPE_APPROX = 100;
2019-07-08 00:13:37 +02:00
private static final int MIN_INCLINE = -101;
2019-07-07 23:13:36 +02:00
private static final int MIN_DIVIDED_INCLINE = -20;
private static final int MAX_INCLINE = 100;
private static final int MAX_DIVIDED_INCLINE = 20;
private static final int STEP = 4;
private static final int[] BOUNDARIES_ARRAY;
2019-07-08 00:13:37 +02:00
private static final String[] BOUNDARIES_CLASS;
2019-07-07 23:13:36 +02:00
static {
2019-07-08 00:13:37 +02:00
int NUM = ((MAX_DIVIDED_INCLINE - MIN_DIVIDED_INCLINE) / STEP) + 3;
2019-07-07 23:13:36 +02:00
BOUNDARIES_ARRAY = new int[NUM];
2019-07-08 00:13:37 +02:00
BOUNDARIES_CLASS = new String[NUM];
2019-07-07 23:13:36 +02:00
BOUNDARIES_ARRAY[0] = MIN_INCLINE;
2019-07-08 00:13:37 +02:00
BOUNDARIES_CLASS[0] = "steepness=" + (MIN_INCLINE + 1) + "_" + MIN_DIVIDED_INCLINE;
2019-07-07 23:13:36 +02:00
for (int i = 1; i < NUM - 1; i++) {
BOUNDARIES_ARRAY[i] = MIN_DIVIDED_INCLINE + (i - 1) * STEP;
2019-07-08 00:37:20 +02:00
BOUNDARIES_CLASS[i] = "steepness=" + (BOUNDARIES_ARRAY[i - 1] + 1) + "_" + BOUNDARIES_ARRAY[i];
2019-07-07 23:13:36 +02:00
}
BOUNDARIES_ARRAY[NUM - 1] = MAX_INCLINE;
2019-07-08 00:13:37 +02:00
BOUNDARIES_CLASS[NUM - 1] = "steepness="+MAX_DIVIDED_INCLINE+"_"+MAX_INCLINE;
2019-07-07 23:13:36 +02:00
}
public static class RouteStatistics {
public final List<RouteSegmentAttribute> elements;
public final Map<String, RouteSegmentAttribute> partition;
public final float totalDistance;
2019-07-08 00:47:57 +02:00
public final String name;
2019-07-07 23:13:36 +02:00
2019-07-08 00:47:57 +02:00
private RouteStatistics(String name, List<RouteSegmentAttribute> elements,
2019-07-07 23:13:36 +02:00
Map<String, RouteSegmentAttribute> partition,
float totalDistance) {
2019-07-08 00:47:57 +02:00
this.name = name.startsWith("routeInfo_") ? name.substring("routeInfo_".length()) : name;
2019-07-07 23:13:36 +02:00
this.elements = elements;
this.partition = partition;
this.totalDistance = totalDistance;
}
2019-07-08 00:47:57 +02:00
@Override
public String toString() {
StringBuilder s = new StringBuilder("Statistic '").append(name).append("':");
for (RouteSegmentAttribute a : elements) {
2019-07-08 01:22:41 +02:00
s.append(String.format(" %.0fm %s,", a.distance, a.getUserPropertyName()));
2019-07-08 00:47:57 +02:00
}
s.append("\n Partition: ").append(partition);
return s.toString();
}
2019-07-07 23:13:36 +02:00
}
2019-07-08 00:47:57 +02:00
2019-07-07 23:13:36 +02:00
private static class RouteSegmentWithIncline {
RouteDataObject obj;
float dist;
float h;
float[] interpolatedHeightByStep;
float[] slopeByStep;
2019-07-08 00:13:37 +02:00
String[] slopeClass;
String[] slopeClassUserString;
2019-07-07 23:13:36 +02:00
}
2019-03-07 18:02:41 +01:00
2019-07-07 18:30:01 +02:00
public static List<RouteStatistics> calculateRouteStatistic(List<RouteSegmentResult> route, RenderingRulesStorage currentRenderer,
RenderingRulesStorage defaultRenderer, RenderingRuleSearchRequest currentSearchRequest,
RenderingRuleSearchRequest defaultSearchRequest) {
2019-07-07 23:13:36 +02:00
List<RouteSegmentWithIncline> routeSegmentWithInclines = calculateInclineRouteSegments(route);
List<String> attributeNames = new ArrayList<>();
2019-07-08 00:37:20 +02:00
if (currentRenderer != null) {
for (String s : currentRenderer.getRenderingAttributeNames()) {
if (s.startsWith("routeInfo_")) {
attributeNames.add(s);
}
2019-07-07 23:13:36 +02:00
}
}
if(attributeNames.isEmpty()) {
for (String s : defaultRenderer.getRenderingAttributeNames()) {
if (s.startsWith("routeInfo_")) {
attributeNames.add(s);
}
}
}
2019-03-07 18:02:41 +01:00
2019-07-07 23:13:36 +02:00
// "steepnessColor", "surfaceColor", "roadClassColor", "smoothnessColor"
// steepness=-19_-16
List<RouteStatistics> result = new ArrayList<>();
for(String attributeName : attributeNames) {
2019-07-07 19:43:13 +02:00
RouteStatisticComputer statisticComputer =
2019-07-07 23:13:36 +02:00
new RouteStatisticComputer(currentRenderer, defaultRenderer, currentSearchRequest, defaultSearchRequest);
result.add(statisticComputer.computeStatistic(routeSegmentWithInclines, attributeName));
2019-07-07 18:30:01 +02:00
}
2019-03-07 18:02:41 +01:00
2019-07-07 18:30:01 +02:00
return result;
2019-03-07 18:02:41 +01:00
}
2019-07-07 23:13:36 +02:00
private static List<RouteSegmentWithIncline> calculateInclineRouteSegments(List<RouteSegmentResult> route) {
List<RouteSegmentWithIncline> input = new ArrayList<>();
float prevHeight = 0;
int totalArrayHeightsLength = 0;
for(RouteSegmentResult r : route) {
float[] heightValues = r.getHeightValues();
RouteSegmentWithIncline incl = new RouteSegmentWithIncline();
incl.dist = r.getDistance();
incl.obj = r.getObject();
input.add(incl);
float prevH = prevHeight;
int indStep = 0;
if(incl.dist > H_STEP) {
2019-07-08 00:13:37 +02:00
// for 10.1 meters 3 points (0, 5, 10)
incl.interpolatedHeightByStep = new float[(int) ((incl.dist) / H_STEP) + 1];
2019-07-07 23:13:36 +02:00
totalArrayHeightsLength += incl.interpolatedHeightByStep.length;
}
if(heightValues != null && heightValues.length > 0) {
int indH = 2;
float distCum = 0;
prevH = heightValues[1];
incl.h = prevH ;
2019-07-08 00:37:20 +02:00
if(incl.interpolatedHeightByStep != null && incl.interpolatedHeightByStep.length > indStep) {
2019-07-08 00:13:37 +02:00
incl.interpolatedHeightByStep[indStep++] = prevH;
}
2019-07-08 00:37:20 +02:00
while(incl.interpolatedHeightByStep != null &&
indStep < incl.interpolatedHeightByStep.length && indH < heightValues.length) {
2019-07-07 23:13:36 +02:00
float dist = heightValues[indH] + distCum;
2019-07-08 00:13:37 +02:00
if(dist > indStep * H_STEP) {
2019-07-07 23:13:36 +02:00
if(dist == distCum) {
incl.interpolatedHeightByStep[indStep] = prevH;
} else {
incl.interpolatedHeightByStep[indStep] = (float) (prevH +
2019-07-08 00:13:37 +02:00
(indStep * H_STEP - distCum) *
2019-07-07 23:13:36 +02:00
(heightValues[indH + 1] - prevH) / (dist - distCum));
}
indStep++;
} else {
distCum = dist;
prevH = heightValues[indH + 1];
indH += 2;
}
}
2019-07-07 19:31:38 +02:00
2019-07-07 23:13:36 +02:00
} else {
incl.h = prevH;
}
2019-07-08 00:37:20 +02:00
while(incl.interpolatedHeightByStep != null &&
indStep < incl.interpolatedHeightByStep.length) {
2019-07-07 23:13:36 +02:00
incl.interpolatedHeightByStep[indStep++] = prevH;
}
prevHeight = prevH;
}
int slopeSmoothShift = (int) (H_SLOPE_APPROX / (2 * H_STEP));
float[] heightArray = new float[totalArrayHeightsLength];
int iter = 0;
for(int i = 0; i < input.size(); i ++) {
RouteSegmentWithIncline rswi = input.get(i);
for(int k = 0; rswi.interpolatedHeightByStep != null &&
k < rswi.interpolatedHeightByStep.length; k++) {
heightArray[iter++] = rswi.interpolatedHeightByStep[k];
}
}
iter = 0;
2019-07-08 00:13:37 +02:00
int minSlope = Integer.MAX_VALUE;
int maxSlope = Integer.MIN_VALUE;
2019-07-07 23:13:36 +02:00
for(int i = 0; i < input.size(); i ++) {
RouteSegmentWithIncline rswi = input.get(i);
if(rswi.interpolatedHeightByStep != null) {
rswi.slopeByStep = new float[rswi.interpolatedHeightByStep.length];
for (int k = 0; k < rswi.slopeByStep.length; k++) {
if (iter > slopeSmoothShift && iter + slopeSmoothShift < heightArray.length) {
2019-07-08 00:13:37 +02:00
double slope = (heightArray[iter + slopeSmoothShift] - heightArray[iter - slopeSmoothShift]) * 100
/ H_SLOPE_APPROX;
rswi.slopeByStep[k] = (float) slope;
minSlope = Math.min((int) slope, minSlope);
maxSlope = Math.max((int) slope, maxSlope);
2019-07-07 23:13:36 +02:00
}
iter++;
}
}
}
2019-07-08 00:13:37 +02:00
String[] classFormattedStrings = new String[BOUNDARIES_ARRAY.length];
classFormattedStrings[0] = formatSlopeString(minSlope, MIN_DIVIDED_INCLINE);
classFormattedStrings[1] = formatSlopeString(minSlope, MIN_DIVIDED_INCLINE);
classFormattedStrings[BOUNDARIES_ARRAY.length - 1] = formatSlopeString(MAX_DIVIDED_INCLINE, maxSlope);
for (int k = 2; k < BOUNDARIES_ARRAY.length - 1; k++) {
classFormattedStrings[k] = formatSlopeString(BOUNDARIES_ARRAY[k - 1], BOUNDARIES_ARRAY[k]);
}
for(int i = 0; i < input.size(); i ++) {
RouteSegmentWithIncline rswi = input.get(i);
if(rswi.slopeByStep != null) {
rswi.slopeClass = new String[rswi.slopeByStep.length];
rswi.slopeClassUserString = new String[rswi.slopeByStep.length];
for (int t = 0; t < rswi.slopeClass.length; t++) {
2019-07-08 00:37:20 +02:00
for (int k = 0; k < BOUNDARIES_ARRAY.length; k++) {
2019-07-08 00:13:37 +02:00
if (rswi.slopeByStep[t] <= BOUNDARIES_ARRAY[k] || k == BOUNDARIES_ARRAY.length - 1) {
2019-07-08 00:37:20 +02:00
rswi.slopeClass[t] = BOUNDARIES_CLASS[k];
rswi.slopeClassUserString[t] = classFormattedStrings[k];
2019-07-08 00:13:37 +02:00
break;
}
}
// end of break
}
}
}
2019-07-07 23:13:36 +02:00
return input;
2019-07-07 19:31:38 +02:00
}
2019-07-07 23:13:36 +02:00
2019-07-08 00:13:37 +02:00
private static String formatSlopeString(int slope, int next) {
return String.format("%d%% .. %d%%", slope, next);
}
2019-07-07 19:43:13 +02:00
private static class RouteStatisticComputer {
2019-03-07 18:02:41 +01:00
final RenderingRulesStorage currentRenderer;
final RenderingRulesStorage defaultRenderer;
final RenderingRuleSearchRequest currentRenderingRuleSearchRequest;
final RenderingRuleSearchRequest defaultRenderingRuleSearchRequest;
2019-03-07 18:02:41 +01:00
2019-07-07 23:13:36 +02:00
RouteStatisticComputer(RenderingRulesStorage currentRenderer, RenderingRulesStorage defaultRenderer,
2019-07-07 18:30:01 +02:00
RenderingRuleSearchRequest currentRenderingRuleSearchRequest, RenderingRuleSearchRequest defaultRenderingRuleSearchRequest) {
this.currentRenderer = currentRenderer;
this.defaultRenderer = defaultRenderer;
this.currentRenderingRuleSearchRequest = currentRenderingRuleSearchRequest;
this.defaultRenderingRuleSearchRequest = defaultRenderingRuleSearchRequest;
2019-03-07 18:02:41 +01:00
}
2019-07-07 23:13:36 +02:00
public RouteStatistics computeStatistic(List<RouteSegmentWithIncline> route, String attribute) {
List<RouteSegmentAttribute> routeAttributes = processRoute(route, attribute);
Map<String, RouteSegmentAttribute> partition = makePartition(routeAttributes);
float totalDistance = computeTotalDistance(routeAttributes);
2019-07-08 00:47:57 +02:00
return new RouteStatistics(attribute, routeAttributes, partition, totalDistance);
2019-07-07 23:13:36 +02:00
}
2019-07-07 19:43:13 +02:00
Map<String, RouteSegmentAttribute> makePartition(List<RouteSegmentAttribute> routeAttributes) {
2019-07-08 08:53:12 +02:00
final Map<String, RouteSegmentAttribute> partition = new TreeMap<>();
2019-07-07 19:43:13 +02:00
for (RouteSegmentAttribute attribute : routeAttributes) {
RouteSegmentAttribute attr = partition.get(attribute.propertyName);
if (attr == null) {
attr = new RouteSegmentAttribute(attribute);
partition.put(attribute.propertyName, attr);
2019-03-07 18:02:41 +01:00
}
2019-07-07 19:43:13 +02:00
attr.incrementDistanceBy(attribute.getDistance());
2019-03-07 18:02:41 +01:00
}
2019-07-08 01:22:41 +02:00
List<String> keys = new ArrayList<String>(partition.keySet());
Collections.sort(keys, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return -Float.compare(partition.get(o1).getDistance(), partition.get(o2).getDistance());
}
});
Map<String, RouteSegmentAttribute> sorted = new LinkedHashMap<String, RouteStatisticsHelper.RouteSegmentAttribute>();
for(String k : keys ) {
sorted.put(k, partition.get(k));
}
return sorted;
2019-03-07 18:02:41 +01:00
}
2019-07-07 19:43:13 +02:00
private float computeTotalDistance(List<RouteSegmentAttribute> attributes) {
2019-03-07 18:02:41 +01:00
float distance = 0f;
for (RouteSegmentAttribute attribute : attributes) {
distance += attribute.getDistance();
}
return distance;
}
2019-07-07 23:13:36 +02:00
protected List<RouteSegmentAttribute> processRoute(List<RouteSegmentWithIncline> route, String attribute) {
2019-07-07 19:31:38 +02:00
List<RouteSegmentAttribute> routes = new ArrayList<>();
2019-07-07 23:13:36 +02:00
RouteSegmentAttribute prev = null;
for (RouteSegmentWithIncline segment : route) {
2019-07-08 00:13:37 +02:00
if(segment.slopeClass == null || segment.slopeClass.length == 0) {
RouteSegmentAttribute current = classifySegment(attribute, null, segment);
current.distance = segment.dist;
if (prev != null && prev.getPropertyName() != null &&
2019-07-07 23:13:36 +02:00
prev.getPropertyName().equals(current.getPropertyName())) {
2019-07-08 00:13:37 +02:00
prev.incrementDistanceBy(current.distance);
} else {
routes.add(current);
prev = current;
}
2019-07-07 23:13:36 +02:00
} else {
2019-07-08 00:13:37 +02:00
for(int i = 0; i < segment.slopeClass.length; i++) {
float d = (float) (i == 0 ? (segment.dist - H_STEP * (segment.slopeClass.length - 1)) : H_STEP);
if(i > 0 && segment.slopeClass[i].equals(segment.slopeClass[i - 1])) {
prev.incrementDistanceBy(d);
} else {
RouteSegmentAttribute current = classifySegment(attribute,
segment.slopeClass[i], segment);
current.distance = d;
if (prev != null && prev.getPropertyName() != null &&
prev.getPropertyName().equals(current.getPropertyName())) {
prev.incrementDistanceBy(current.distance);
} else {
2019-07-08 01:22:41 +02:00
if(segment.slopeClass[i].endsWith(current.propertyName)) {
current.setUserPropertyName(segment.slopeClassUserString[i]);
}
2019-07-08 00:13:37 +02:00
routes.add(current);
prev = current;
}
}
}
2019-03-07 18:02:41 +01:00
}
}
return routes;
}
2019-07-07 19:43:13 +02:00
2019-07-08 00:13:37 +02:00
public RouteSegmentAttribute classifySegment(String attribute, String slopeClass, RouteSegmentWithIncline segment) {
2019-07-07 23:13:36 +02:00
RouteSegmentAttribute res = new RouteSegmentAttribute(UNDEFINED_ATTR, 0);
2019-07-08 00:37:20 +02:00
RenderingRuleSearchRequest currentRequest =
currentRenderer == null ? null : new RenderingRuleSearchRequest(currentRenderingRuleSearchRequest);
if (currentRenderer != null && searchRenderingAttribute(attribute, currentRenderer, currentRequest, segment, slopeClass)) {
2019-07-07 23:13:36 +02:00
res = new RouteSegmentAttribute(currentRequest.getStringPropertyValue(currentRenderer.PROPS.R_ATTR_STRING_VALUE),
currentRequest.getIntPropertyValue(currentRenderer.PROPS.R_ATTR_COLOR_VALUE));
} else {
2019-07-07 23:13:36 +02:00
RenderingRuleSearchRequest defaultRequest = new RenderingRuleSearchRequest(defaultRenderingRuleSearchRequest);
2019-07-08 00:13:37 +02:00
if (searchRenderingAttribute(attribute, defaultRenderer, defaultRequest, segment, slopeClass)) {
2019-07-08 01:22:41 +02:00
res = new RouteSegmentAttribute(
defaultRequest.getStringPropertyValue(defaultRenderer.PROPS.R_ATTR_STRING_VALUE),
2019-07-07 23:13:36 +02:00
defaultRequest.getIntPropertyValue(defaultRenderer.PROPS.R_ATTR_COLOR_VALUE));
}
}
2019-07-07 19:31:38 +02:00
return res;
}
2019-03-07 18:02:41 +01:00
2019-07-07 23:13:36 +02:00
protected boolean searchRenderingAttribute(String attribute,
2019-07-08 00:13:37 +02:00
RenderingRulesStorage rrs, RenderingRuleSearchRequest req, RouteSegmentWithIncline segment,
String slopeClass) {
2019-07-07 19:31:38 +02:00
//String additional = attrName + "=" + attribute;
2019-07-07 23:13:36 +02:00
RouteDataObject obj = segment.obj;
2019-07-07 19:31:38 +02:00
int[] tps = obj.getTypes();
2019-07-08 01:30:33 +02:00
String additional = slopeClass != null ? (slopeClass + ";") : "";
2019-07-07 19:58:44 +02:00
for (int k = 0; k < tps.length; k++) {
2019-07-07 19:31:38 +02:00
BinaryMapRouteReaderAdapter.RouteTypeRule tp = obj.region.quickGetEncodingRule(tps[k]);
2019-07-07 19:58:44 +02:00
if (tp.getTag().equals("highway") || tp.getTag().equals("route") ||
2019-07-07 19:31:38 +02:00
tp.getTag().equals("railway") || tp.getTag().equals("aeroway") || tp.getTag().equals("aerialway")) {
req.setStringFilter(rrs.PROPS.R_TAG, tp.getTag());
2019-07-08 00:47:57 +02:00
req.setStringFilter(rrs.PROPS.R_VALUE, tp.getValue());
2019-07-07 19:31:38 +02:00
} else {
2019-07-08 01:30:33 +02:00
additional += tp.getTag() + "=" + tp.getValue() + ";";
2019-07-07 19:31:38 +02:00
}
}
req.setStringFilter(rrs.PROPS.R_ADDITIONAL, additional);
2019-07-07 23:13:36 +02:00
return req.searchRenderingAttribute(attribute);
2019-07-07 19:31:38 +02:00
}
2019-03-07 18:02:41 +01:00
}
2019-07-07 19:31:38 +02:00
public static class RouteSegmentAttribute {
2019-03-07 18:02:41 +01:00
private final int color;
private final String propertyName;
private float distance;
2019-07-08 01:22:41 +02:00
private String userPropertyName;
2019-03-07 18:02:41 +01:00
2019-07-07 23:13:36 +02:00
RouteSegmentAttribute(String propertyName, int color) {
2019-07-08 00:47:57 +02:00
this.propertyName = propertyName == null ? UNDEFINED_ATTR : propertyName;
2019-03-07 18:02:41 +01:00
this.color = color;
}
2019-07-07 19:31:38 +02:00
RouteSegmentAttribute(RouteSegmentAttribute segmentAttribute) {
2019-03-07 18:02:41 +01:00
this.propertyName = segmentAttribute.getPropertyName();
this.color = segmentAttribute.getColor();
2019-07-08 01:22:41 +02:00
this.userPropertyName = segmentAttribute.userPropertyName;
}
public String getUserPropertyName() {
return userPropertyName == null ? propertyName : userPropertyName;
}
public void setUserPropertyName(String userPropertyName) {
this.userPropertyName = userPropertyName;
2019-03-07 18:02:41 +01:00
}
public float getDistance() {
return distance;
}
public void incrementDistanceBy(float distance) {
this.distance += distance;
}
public String getPropertyName() {
return propertyName;
}
public int getColor() {
return color;
}
@Override
public String toString() {
2019-07-08 01:22:41 +02:00
return String.format("%s - %.0f m %d", getUserPropertyName(), getDistance(), getColor());
2019-03-01 12:35:59 +01:00
}
}
2019-03-07 18:02:41 +01:00
}