Merge pull request #6228 from osmandapp/road-surface
split route by surface type
This commit is contained in:
commit
ed81bc438c
7 changed files with 902 additions and 10 deletions
|
@ -1718,4 +1718,6 @@ public class RouteResultPreparation {
|
|||
return MapUtils.getDistance(MapUtils.get31LatitudeY(y1), MapUtils.get31LongitudeX(x1),
|
||||
MapUtils.get31LatitudeY(y2), MapUtils.get31LongitudeX(x2));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package net.osmand.router;
|
|||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -43,19 +44,28 @@ public class RouteSegmentResult {
|
|||
int st = Math.min(startPointIndex, endPointIndex);
|
||||
int end = Math.max(startPointIndex, endPointIndex);
|
||||
float[] res = new float[(end - st + 1) * 2];
|
||||
for (int k = 0; k < res.length / 2; k++) {
|
||||
int ind = reverse ? (2 * (end - k)) : (2 * (k + st));
|
||||
if (k == 0) {
|
||||
res[2 * k] = 0;
|
||||
} else {
|
||||
if(ind < pf.length) {
|
||||
res[2 * k] = pf[k];
|
||||
if (reverse) {
|
||||
for (int k = 1; k <= res.length / 2; k++) {
|
||||
int ind = (2 * (end--));
|
||||
if (ind < pf.length && k < res.length / 2) {
|
||||
res[2 * k] = pf[ind];
|
||||
}
|
||||
if (ind < pf.length) {
|
||||
res[2 * (k - 1) + 1] = pf[ind + 1];
|
||||
}
|
||||
}
|
||||
if(ind < pf.length) {
|
||||
res[2 * k + 1] = pf[ind + 1];
|
||||
} else {
|
||||
for (int k = 0; k < res.length / 2; k++) {
|
||||
int ind = (2 * (st + k));
|
||||
if (k > 0 && ind < pf.length) {
|
||||
res[2 * k] = pf[ind];
|
||||
}
|
||||
if (ind < pf.length) {
|
||||
res[2 * k + 1] = pf[ind + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -218,4 +228,15 @@ public class RouteSegmentResult {
|
|||
return object.toString() + ": " + startPointIndex + "-" + endPointIndex;
|
||||
}
|
||||
|
||||
public String getSurface() {
|
||||
return object.getValue("surface");
|
||||
}
|
||||
|
||||
public String getSmoothness() {
|
||||
return object.getValue("smoothness");
|
||||
}
|
||||
|
||||
public String getHighway() {
|
||||
return object.getHighway();
|
||||
}
|
||||
}
|
530
OsmAnd-java/src/main/java/net/osmand/router/RouteStatistics.java
Normal file
530
OsmAnd-java/src/main/java/net/osmand/router/RouteStatistics.java
Normal file
|
@ -0,0 +1,530 @@
|
|||
package net.osmand.router;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class RouteStatistics {
|
||||
|
||||
private final List<RouteSegmentResult> route;
|
||||
|
||||
private RouteStatistics(List<RouteSegmentResult> route) {
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
public static RouteStatistics newRouteStatistic(List<RouteSegmentResult> route) {
|
||||
return new RouteStatistics(route);
|
||||
}
|
||||
|
||||
public Statistics getRouteSurfaceStatistic() {
|
||||
RouteStatisticComputer statisticComputer = new RouteSurfaceStatisticComputer(route);
|
||||
return statisticComputer.computeStatistic();
|
||||
}
|
||||
|
||||
public Statistics getRouteSmoothnessStatistic() {
|
||||
RouteStatisticComputer statisticComputer = new RouteSmoothnessStatisticComputer(route);
|
||||
return statisticComputer.computeStatistic();
|
||||
}
|
||||
|
||||
public Statistics getRouteClassStatistic() {
|
||||
RouteStatisticComputer statisticComputer = new RouteClassStatisticComputer(route);
|
||||
return statisticComputer.computeStatistic();
|
||||
}
|
||||
|
||||
public Statistics getRouteSteepnessStatistic(List<Incline> inclines) {
|
||||
RouteStatisticComputer statisticComputer = new RouteSteepnessStatisticComputer(inclines);
|
||||
return statisticComputer.computeStatistic();
|
||||
}
|
||||
|
||||
|
||||
private abstract static class RouteStatisticComputer<E extends Comparable<E>> {
|
||||
|
||||
private final List<RouteSegmentResult> route;
|
||||
|
||||
public RouteStatisticComputer(List<RouteSegmentResult> route) {
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
protected Map<E, RouteSegmentAttribute<E>> makePartition(List<RouteSegmentAttribute<E>> routeAttributes) {
|
||||
Map<E, RouteSegmentAttribute<E>> partition = new TreeMap<>();
|
||||
for (RouteSegmentAttribute<E> attribute : routeAttributes) {
|
||||
E key = attribute.getAttribute();
|
||||
RouteSegmentAttribute<E> pattr = partition.get(key);
|
||||
if (pattr == null) {
|
||||
pattr = new RouteSegmentAttribute<>(attribute.getIndex(), attribute.getAttribute(), attribute.getColorAttrName());
|
||||
partition.put(key, pattr);
|
||||
}
|
||||
pattr.incrementDistanceBy(attribute.getDistance());
|
||||
}
|
||||
return partition;
|
||||
}
|
||||
|
||||
private float computeTotalDistance(List<RouteSegmentAttribute<E>> attributes) {
|
||||
float distance = 0f;
|
||||
for (RouteSegmentAttribute attribute : attributes) {
|
||||
distance += attribute.getDistance();
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
protected List<RouteSegmentResult> getRoute() {
|
||||
return route;
|
||||
}
|
||||
|
||||
protected List<RouteSegmentAttribute<E>> processRoute() {
|
||||
int index = 0;
|
||||
List<RouteSegmentAttribute<E>> routes = new ArrayList<>();
|
||||
E prev = null;
|
||||
for (RouteSegmentResult segment : getRoute()) {
|
||||
E current = getAttribute(segment);
|
||||
if (prev != null && !prev.equals(current)) {
|
||||
index++;
|
||||
}
|
||||
if (index >= routes.size()) {
|
||||
String colorAttrName = determineColor(current);
|
||||
routes.add(new RouteSegmentAttribute<>(index, current, colorAttrName));
|
||||
}
|
||||
RouteSegmentAttribute surface = routes.get(index);
|
||||
surface.incrementDistanceBy(segment.getDistance());
|
||||
prev = current;
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
public Statistics<E> computeStatistic() {
|
||||
List<RouteSegmentAttribute<E>> routeAttributes = processRoute();
|
||||
Map<E, RouteSegmentAttribute<E>> partition = makePartition(routeAttributes);
|
||||
float totalDistance = computeTotalDistance(routeAttributes);
|
||||
return new Statistics<>(routeAttributes, partition, totalDistance);
|
||||
}
|
||||
|
||||
public abstract E getAttribute(RouteSegmentResult segment);
|
||||
|
||||
public abstract String determineColor(E attribute);
|
||||
|
||||
}
|
||||
|
||||
private static class RouteSurfaceStatisticComputer extends RouteStatisticComputer<String> {
|
||||
|
||||
public RouteSurfaceStatisticComputer(List<RouteSegmentResult> route) {
|
||||
super(route);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(RouteSegmentResult segment) {
|
||||
String segmentSurface = segment.getSurface();
|
||||
if (segmentSurface == null) {
|
||||
return RoadSurface.UNDEFINED.name().toLowerCase();
|
||||
}
|
||||
for (RoadSurface roadSurface : RoadSurface.values()) {
|
||||
if (roadSurface.contains(segmentSurface)) {
|
||||
return roadSurface.name().toLowerCase();
|
||||
}
|
||||
}
|
||||
return RoadSurface.UNDEFINED.name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String determineColor(String attribute) {
|
||||
RoadSurface roadSurface = RoadSurface.valueOf(attribute.toUpperCase());
|
||||
return roadSurface.getColorAttrName();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RouteSmoothnessStatisticComputer extends RouteStatisticComputer<String> {
|
||||
|
||||
public RouteSmoothnessStatisticComputer(List<RouteSegmentResult> route) {
|
||||
super(route);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(RouteSegmentResult segment) {
|
||||
String segmentSmoothness = segment.getSurface();
|
||||
if (segmentSmoothness == null) {
|
||||
return RoadSmoothness.UNDEFINED.name().toLowerCase();
|
||||
}
|
||||
for (RoadSmoothness roadSmoothness : RoadSmoothness.values()) {
|
||||
if (roadSmoothness.contains(segmentSmoothness)) {
|
||||
return roadSmoothness.name().toLowerCase();
|
||||
}
|
||||
}
|
||||
return RoadSmoothness.UNDEFINED.name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String determineColor(String attribute) {
|
||||
RoadSmoothness roadSmoothness = RoadSmoothness.valueOf(attribute.toUpperCase());
|
||||
return roadSmoothness.getColorAttrName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class RouteClassStatisticComputer extends RouteStatisticComputer<String> {
|
||||
|
||||
public RouteClassStatisticComputer(List<RouteSegmentResult> route) {
|
||||
super(route);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(RouteSegmentResult segment) {
|
||||
String segmentClass = segment.getHighway();
|
||||
if (segmentClass == null) {
|
||||
return RoadClass.UNDEFINED.name().toLowerCase();
|
||||
}
|
||||
for (RoadClass roadClass : RoadClass.values()) {
|
||||
if (roadClass.contains(segmentClass)) {
|
||||
return roadClass.name().toLowerCase();
|
||||
}
|
||||
}
|
||||
return RoadClass.UNDEFINED.name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String determineColor(String attribute) {
|
||||
RoadClass roadClass = RoadClass.valueOf(attribute.toUpperCase());
|
||||
return roadClass.getColorAttrName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class RouteSteepnessStatisticComputer extends RouteStatisticComputer<Boundaries> {
|
||||
|
||||
private static final String POSITIVE_INCLINE_COLOR_ATTR_NAME = "greenColor";
|
||||
private static final String NEGATIVE_INCLINE_COLOR_ATTR_NAME = "redColor";
|
||||
|
||||
private final List<Incline> inclines;
|
||||
|
||||
public RouteSteepnessStatisticComputer(List<Incline> inclines) {
|
||||
super(null);
|
||||
this.inclines = inclines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RouteSegmentAttribute<Boundaries>> processRoute() {
|
||||
List<RouteSegmentAttribute<Boundaries>> routeInclines = new ArrayList<>();
|
||||
int index = 0;
|
||||
Boundaries prev = null;
|
||||
Incline prevIncline = null;
|
||||
for (Incline incline : inclines) {
|
||||
Boundaries current = incline.getBoundaries();
|
||||
if (prev != null && !prev.equals(current)) {
|
||||
index++;
|
||||
}
|
||||
if (index >= routeInclines.size()) {
|
||||
String colorAttrName = determineColor(current);
|
||||
RouteSegmentAttribute<Boundaries> attribute = new RouteSegmentAttribute<>(index, current, colorAttrName);
|
||||
if (prevIncline != null) {
|
||||
attribute.setInitDistance(prevIncline.getDistance());
|
||||
}
|
||||
routeInclines.add(attribute);
|
||||
}
|
||||
RouteSegmentAttribute routeIncline = routeInclines.get(index);
|
||||
routeIncline.relativeSum(incline.getDistance());
|
||||
prev = current;
|
||||
prevIncline = incline;
|
||||
}
|
||||
return routeInclines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boundaries getAttribute(RouteSegmentResult segment) {
|
||||
/*
|
||||
no-op
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String determineColor(Boundaries attribute) {
|
||||
return attribute.getLowerBoundary() >= 0 ? POSITIVE_INCLINE_COLOR_ATTR_NAME : NEGATIVE_INCLINE_COLOR_ATTR_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class RouteSegmentAttribute<E> {
|
||||
|
||||
private final int index;
|
||||
private final E attribute;
|
||||
private final String colorAttrName;
|
||||
|
||||
private float distance;
|
||||
private float initDistance;
|
||||
|
||||
public RouteSegmentAttribute(int index, E attribute, String colorAttrName) {
|
||||
this.index = index;
|
||||
this.attribute = attribute;
|
||||
this.colorAttrName = colorAttrName;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public E getAttribute() {
|
||||
return attribute;
|
||||
}
|
||||
|
||||
public float getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public void setInitDistance(float initDistance) {
|
||||
this.initDistance = initDistance;
|
||||
}
|
||||
|
||||
public void incrementDistanceBy(float distance) {
|
||||
this.distance += distance;
|
||||
}
|
||||
|
||||
public void relativeSum(float distance) {
|
||||
this.distance = this.distance + ((distance - this.initDistance) - this.distance);
|
||||
}
|
||||
|
||||
public String getColorAttrName() {
|
||||
return colorAttrName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RouteSegmentAttribute{" +
|
||||
"index=" + index +
|
||||
", attribute='" + attribute + '\'' +
|
||||
", colorAttrName='" + colorAttrName + '\'' +
|
||||
", distance=" + distance +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Incline {
|
||||
|
||||
private float inclineValue;
|
||||
private final float distance;
|
||||
private final Boundaries boundaries;
|
||||
|
||||
public Incline(float inclineValue, float distance) {
|
||||
this.inclineValue = inclineValue;
|
||||
this.distance = distance;
|
||||
this.boundaries = Boundaries.newBoundariesFor(inclineValue);
|
||||
}
|
||||
|
||||
public float getValue() {
|
||||
return inclineValue;
|
||||
}
|
||||
|
||||
public float getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public Boundaries getBoundaries() {
|
||||
return this.boundaries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Incline{" +
|
||||
", incline=" + inclineValue +
|
||||
", distance=" + distance +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Boundaries implements Comparable<Boundaries> {
|
||||
|
||||
private static final int MIN_INCLINE = -100;
|
||||
private static final int MAX_INCLINE = 100;
|
||||
private static final int STEP = 4;
|
||||
private static final int NUM;
|
||||
private static final int[] BOUNDARIES_ARRAY;
|
||||
|
||||
static {
|
||||
NUM = ((MAX_INCLINE - MIN_INCLINE) / STEP + 1);
|
||||
BOUNDARIES_ARRAY = new int[NUM];
|
||||
for (int i = 0; i < NUM; i++) {
|
||||
BOUNDARIES_ARRAY[i] = MIN_INCLINE + i * STEP;
|
||||
}
|
||||
}
|
||||
|
||||
private final float upperBoundary;
|
||||
private final float lowerBoundary;
|
||||
|
||||
private Boundaries(float upperBoundary, float lowerBoundary) {
|
||||
this.upperBoundary = upperBoundary;
|
||||
this.lowerBoundary = lowerBoundary;
|
||||
}
|
||||
|
||||
public static Boundaries newBoundariesFor(float incline) {
|
||||
if (incline > MAX_INCLINE) {
|
||||
return new Boundaries(MAX_INCLINE, MAX_INCLINE - STEP);
|
||||
}
|
||||
if (incline < MIN_INCLINE) {
|
||||
return new Boundaries(MIN_INCLINE + STEP, MIN_INCLINE);
|
||||
}
|
||||
for (int i = 1; i < NUM; i++) {
|
||||
if (incline >= BOUNDARIES_ARRAY[i - 1] && incline < BOUNDARIES_ARRAY[i]) {
|
||||
return new Boundaries(BOUNDARIES_ARRAY[i], BOUNDARIES_ARRAY[i - 1]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public float getUpperBoundary() {
|
||||
return upperBoundary;
|
||||
}
|
||||
|
||||
public float getLowerBoundary() {
|
||||
return lowerBoundary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Boundaries that = (Boundaries) o;
|
||||
|
||||
if (Float.compare(that.upperBoundary, upperBoundary) != 0) return false;
|
||||
return Float.compare(that.lowerBoundary, lowerBoundary) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (upperBoundary != +0.0f ? Float.floatToIntBits(upperBoundary) : 0);
|
||||
result = 31 * result + (lowerBoundary != +0.0f ? Float.floatToIntBits(lowerBoundary) : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Boundaries boundaries) {
|
||||
return (int) (getLowerBoundary() - boundaries.getLowerBoundary());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%d-%d", Math.round(getLowerBoundary()), Math.round(getUpperBoundary()));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Statistics<E> {
|
||||
|
||||
private final List<RouteSegmentAttribute<E>> elements;
|
||||
private final Map<E, RouteSegmentAttribute<E>> partition;
|
||||
private final float totalDistance;
|
||||
|
||||
private Statistics(List<RouteSegmentAttribute<E>> elements,
|
||||
Map<E, RouteSegmentAttribute<E>> partition,
|
||||
float totalDistance) {
|
||||
this.elements = elements;
|
||||
this.partition = partition;
|
||||
this.totalDistance = totalDistance;
|
||||
}
|
||||
|
||||
public float getTotalDistance() {
|
||||
return totalDistance;
|
||||
}
|
||||
|
||||
public List<RouteSegmentAttribute<E>> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public Map<E, RouteSegmentAttribute<E>> getPartition() {
|
||||
return partition;
|
||||
}
|
||||
}
|
||||
|
||||
public enum RoadClass {
|
||||
UNDEFINED("whitewaterSectionGrade0Color", "undefined"),
|
||||
MOTORWAY("motorwayRoadColor", "motorway", "motorway_link"),
|
||||
STATE_ROAD("trunkRoadColor" , "trunk", "trunk_link", "primary", "primary_link"),
|
||||
ROAD("secondaryRoadColor", "secondary", "secondary_link", "tertiary", "tertiary_link", "unclassified"),
|
||||
STREET("residentialRoadColor" ,"residential", "living_street"),
|
||||
SERVICE("serviceRoadColor", "service"),
|
||||
TRACK("trackColor", "track", "road"),
|
||||
FOOTWAY("footwayColor", "footway"),
|
||||
PATH("pathColor", "path"),
|
||||
CYCLE_WAY("cyclewayColor", "cycleway");
|
||||
|
||||
final Set<String> roadClasses = new TreeSet<>();
|
||||
final String colorAttrName;
|
||||
|
||||
RoadClass(String colorAttrName, String... classes) {
|
||||
roadClasses.addAll(Arrays.asList(classes));
|
||||
this.colorAttrName = colorAttrName;
|
||||
}
|
||||
|
||||
boolean contains(String roadClass) {
|
||||
return roadClasses.contains(roadClass);
|
||||
}
|
||||
|
||||
String getColorAttrName() {
|
||||
return colorAttrName;
|
||||
}
|
||||
}
|
||||
|
||||
public enum RoadSurface {
|
||||
UNDEFINED("whitewaterSectionGrade0Color", "undefined"),
|
||||
PAVED("motorwayRoadColor", "paved"),
|
||||
UNPAVED("motorwayRoadShadowColor", "unpaved"),
|
||||
ASPHALT("trunkRoadColor", "asphalt"),
|
||||
CONCRETE("primaryRoadColor", "concrete"),
|
||||
COMPACTED("secondaryRoadColor", "compacted"),
|
||||
GRAVEL("tertiaryRoadColor", "gravel"),
|
||||
FINE_GRAVEL("residentialRoadColor", "fine_gravel"),
|
||||
PAVING_STONES("serviceRoadColor", "paving_stones"),
|
||||
SETT("roadRoadColor", "sett"),
|
||||
COBBLESTONE("pedestrianRoadColor", "cobblestone"),
|
||||
PEBBLESTONE("racewayColor", "pebblestone"),
|
||||
STONE("trackColor", "stone"),
|
||||
METAL("footwayColor", "metal"),
|
||||
GROUND("pathColor", "ground", "mud"),
|
||||
WOOD("cycleRouteColor", "wood"),
|
||||
GRASS_PAVER("osmcBlackColor", "grass_paver"),
|
||||
GRASS("osmcBlueColor", "grass"),
|
||||
SAND("osmcGreenColor", "sand"),
|
||||
SALT("osmcRedColor", "salt"),
|
||||
SNOW("osmcYellowColor", "snow"),
|
||||
ICE("osmcOrangeColor", "ice"),
|
||||
CLAY("osmcBrownColor", "clay");
|
||||
|
||||
final Set<String> surfaces = new TreeSet<>();
|
||||
final String colorAttrName;
|
||||
|
||||
RoadSurface(String colorAttrName, String... surfaces) {
|
||||
this.surfaces.addAll(Arrays.asList(surfaces));
|
||||
this.colorAttrName = colorAttrName;
|
||||
}
|
||||
|
||||
boolean contains(String surface) {
|
||||
return surfaces.contains(surface);
|
||||
}
|
||||
|
||||
public String getColorAttrName() {
|
||||
return this.colorAttrName;
|
||||
}
|
||||
}
|
||||
|
||||
public enum RoadSmoothness {
|
||||
UNDEFINED("redColor", "undefined"),
|
||||
EXCELLENT("orangeColor", "excellent"),
|
||||
GOOD("brownColor", "good"),
|
||||
INTERMEDIATE("darkyellowColor", "intermediate"),
|
||||
BAD("yellowColor", "bad"),
|
||||
VERY_BAD("lightgreenColor", "very_bad"),
|
||||
HORRIBLE("greenColor", "horrible"),
|
||||
VERY_HORRIBLE("lightblueColor", "very_horrible"),
|
||||
IMPASSABLE("blueColor", "impassable");
|
||||
|
||||
final Set<String> surfaces = new TreeSet<>();
|
||||
final String colorAttrName;
|
||||
|
||||
RoadSmoothness(String colorAttrName, String... surfaces) {
|
||||
this.surfaces.addAll(Arrays.asList(surfaces));
|
||||
this.colorAttrName = colorAttrName;
|
||||
}
|
||||
|
||||
boolean contains(String surface) {
|
||||
return surfaces.contains(surface);
|
||||
}
|
||||
|
||||
public String getColorAttrName() {
|
||||
return this.colorAttrName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -252,6 +252,187 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:background="?attr/bg_color"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/route_class_stat_container"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center_horizontal"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.github.mikephil.charting.charts.HorizontalBarChart
|
||||
android:id="@+id/route_class_stat_chart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/route_class_stat_items"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:background="?attr/bg_color"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/route_surface_stat_container"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center_horizontal"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.github.mikephil.charting.charts.HorizontalBarChart
|
||||
android:id="@+id/route_surface_stat_chart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/route_surface_stat_items"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:background="?attr/bg_color"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/route_smoothness_stat_container"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center_horizontal"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.github.mikephil.charting.charts.HorizontalBarChart
|
||||
android:id="@+id/route_smoothness_stat_chart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/route_smoothness_stat_items"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:background="?attr/bg_color"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/route_steepness_stat_container"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center_horizontal"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.github.mikephil.charting.charts.HorizontalBarChart
|
||||
android:id="@+id/route_steepness_stat_chart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/route_steepness_stat_items"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/bg_color"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
|
|
29
OsmAnd/res/layout/route_info_stat_item.xml
Normal file
29
OsmAnd/res/layout/route_info_stat_item.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/route_info_stat_item">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:src="@drawable/ic_action_circle"
|
||||
android:id="@+id/route_stat_item_image"
|
||||
android:paddingLeft="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/route_stat_item_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="18dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -2993,6 +2993,10 @@
|
|||
<string name="wiki_article_not_found">Article not found</string>
|
||||
<string name="how_to_open_wiki_title">How to open Wikipedia articles?</string>
|
||||
<string name="test_voice_desrc">Tap a button and listen to the corresponding voice prompt to identify missing or faulty prompts.</string>
|
||||
<string name="route_class_stat_container">Class</string>
|
||||
<string name="route_surface_stat_container">Surface</string>
|
||||
<string name="route_smoothness_stat_container">Smoothenss</string>
|
||||
<string name="route_steepness_stat_container">Steepness</string>
|
||||
<string name="run_full_osmand_msg">You are using {0} Map which is powered by OsmAnd. Do you want to launch OsmAnd full version?</string>
|
||||
<string name="run_full_osmand_header">Launch OsmAnd?</string>
|
||||
</resources>
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.graphics.Color;
|
|||
import android.graphics.Matrix;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -27,12 +28,23 @@ import android.widget.ImageView;
|
|||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.mikephil.charting.charts.HorizontalBarChart;
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
import com.github.mikephil.charting.components.AxisBase;
|
||||
import com.github.mikephil.charting.components.XAxis;
|
||||
import com.github.mikephil.charting.components.YAxis;
|
||||
import com.github.mikephil.charting.data.BarData;
|
||||
import com.github.mikephil.charting.data.BarDataSet;
|
||||
import com.github.mikephil.charting.data.BarEntry;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
||||
import com.github.mikephil.charting.highlight.Highlight;
|
||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
||||
import com.github.mikephil.charting.listener.ChartTouchListener;
|
||||
import com.github.mikephil.charting.listener.OnChartGestureListener;
|
||||
import com.github.mikephil.charting.utils.ViewPortHandler;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.Location;
|
||||
|
@ -58,6 +70,12 @@ import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu;
|
|||
import net.osmand.plus.routing.RouteDirectionInfo;
|
||||
import net.osmand.plus.routing.RoutingHelper;
|
||||
import net.osmand.plus.views.TurnPathHelper;
|
||||
import net.osmand.render.RenderingRuleSearchRequest;
|
||||
import net.osmand.render.RenderingRulesStorage;
|
||||
import net.osmand.router.RouteStatistics;
|
||||
import net.osmand.router.RouteStatistics.Incline;
|
||||
import net.osmand.router.RouteStatistics.RouteSegmentAttribute;
|
||||
import net.osmand.router.RouteStatistics.Statistics;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -69,6 +87,7 @@ import java.util.ArrayList;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class ShowRouteInfoDialogFragment extends DialogFragment {
|
||||
|
@ -188,9 +207,115 @@ public class ShowRouteInfoDialogFragment extends DialogFragment {
|
|||
listView.addHeaderView(headerView);
|
||||
}
|
||||
|
||||
List<Incline> inclines = createInclinesAndAdd100MetersWith0Incline(slopeDataSet.getValues());
|
||||
|
||||
RouteStatistics routeStatistics = RouteStatistics.newRouteStatistic(helper.getRoute().getOriginalRoute());
|
||||
buildChartAndAttachLegend(app, view, inflater, R.id.route_class_stat_chart,
|
||||
R.id.route_class_stat_items, routeStatistics.getRouteClassStatistic());
|
||||
buildChartAndAttachLegend(app, view, inflater, R.id.route_surface_stat_chart,
|
||||
R.id.route_surface_stat_items, routeStatistics.getRouteSurfaceStatistic());
|
||||
buildChartAndAttachLegend(app, view, inflater, R.id.route_smoothness_stat_chart,
|
||||
R.id.route_smoothness_stat_items, routeStatistics.getRouteSmoothnessStatistic());
|
||||
buildChartAndAttachLegend(app, view, inflater, R.id.route_steepness_stat_chart,
|
||||
R.id.route_steepness_stat_items, routeStatistics.getRouteSteepnessStatistic(inclines));
|
||||
return view;
|
||||
}
|
||||
|
||||
private List<Incline> createInclinesAndAdd100MetersWith0Incline(List<Entry> entries) {
|
||||
int size = entries.size();
|
||||
List<Incline> inclines = new ArrayList<>();
|
||||
for (Entry entry : entries) {
|
||||
Incline incline = new Incline(entry.getY(), entry.getX() * 1000);
|
||||
inclines.add(incline);
|
||||
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
float distance = i * 5;
|
||||
inclines.add(i, new Incline(0f, distance));
|
||||
}
|
||||
float lastDistance = slopeDataSet.getEntryForIndex(size - 1).getX();
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
float distance = lastDistance * 1000f + i * 5f;
|
||||
inclines.add(new Incline(0f, distance));
|
||||
}
|
||||
return inclines;
|
||||
}
|
||||
|
||||
private int getColorFromStyle(String colorAttrName) {
|
||||
RenderingRulesStorage rrs = getMyApplication().getRendererRegistry().getCurrentSelectedRenderer();
|
||||
RenderingRuleSearchRequest req = new RenderingRuleSearchRequest(rrs);
|
||||
boolean nightMode = false;
|
||||
req.setBooleanFilter(rrs.PROPS.R_NIGHT_MODE, nightMode);
|
||||
if (req.searchRenderingAttribute(colorAttrName)) {
|
||||
return req.getIntPropertyValue(rrs.PROPS.R_ATTR_COLOR_VALUE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private <E> void buildStatisticChart(View view, int chartId, Statistics<E> routeStatistics) {
|
||||
List<RouteSegmentAttribute<E>> segments = routeStatistics.getElements();
|
||||
HorizontalBarChart hbc = view.findViewById(chartId);
|
||||
List<BarEntry> entries = new ArrayList<>();
|
||||
float[] stacks = new float[segments.size()];
|
||||
int[] colors = new int[segments.size()];
|
||||
for (int i = 0; i < stacks.length; i++) {
|
||||
RouteSegmentAttribute segment = segments.get(i);
|
||||
stacks[i] = segment.getDistance();
|
||||
colors[i] = getColorFromStyle(segment.getColorAttrName());
|
||||
}
|
||||
entries.add(new BarEntry(0, stacks));
|
||||
BarDataSet barDataSet = new BarDataSet(entries, "");
|
||||
barDataSet.setColors(colors);
|
||||
BarData data = new BarData(barDataSet);
|
||||
data.setDrawValues(false);
|
||||
hbc.setData(data);
|
||||
hbc.setDrawBorders(false);
|
||||
hbc.setTouchEnabled(false);
|
||||
hbc.disableScroll();
|
||||
hbc.getLegend().setEnabled(false);
|
||||
hbc.getDescription().setEnabled(false);
|
||||
XAxis xAxis = hbc.getXAxis();
|
||||
xAxis.setEnabled(false);
|
||||
YAxis leftYAxis = hbc.getAxisLeft();
|
||||
YAxis rightYAxis = hbc.getAxisRight();
|
||||
rightYAxis.setDrawLabels(true);
|
||||
rightYAxis.setGranularity(1f);
|
||||
rightYAxis.setValueFormatter(new IAxisValueFormatter() {
|
||||
@Override
|
||||
public String getFormattedValue(float value, AxisBase axis) {
|
||||
if (value > 100) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
});
|
||||
rightYAxis.setDrawGridLines(false);
|
||||
leftYAxis.setDrawLabels(false);
|
||||
leftYAxis.setEnabled(false);
|
||||
hbc.invalidate();
|
||||
}
|
||||
|
||||
private <E> void attachLegend(OsmandApplication app, LayoutInflater inflater, ViewGroup container, Statistics<E> routeStatistics) {
|
||||
Map<E, RouteSegmentAttribute<E>> partition = routeStatistics.getPartition();
|
||||
for (E key : partition.keySet()) {
|
||||
RouteSegmentAttribute<E> segment = partition.get(key);
|
||||
View view = inflater.inflate(R.layout.route_info_stat_item, container, false);
|
||||
TextView textView = view.findViewById(R.id.route_stat_item_text);
|
||||
String formattedDistance = OsmAndFormatter.getFormattedDistance(segment.getDistance(), getMyApplication());
|
||||
textView.setText(String.format("%s - %s", key, formattedDistance));
|
||||
Drawable circle = app.getUIUtilities().getPaintedIcon(R.drawable.ic_action_circle,getColorFromStyle(segment.getColorAttrName()));
|
||||
ImageView imageView = view.findViewById(R.id.route_stat_item_image);
|
||||
imageView.setImageDrawable(circle);
|
||||
container.addView(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildChartAndAttachLegend(OsmandApplication app, View view, LayoutInflater inflater, int chartId, int containerId, Statistics routeStatistics) {
|
||||
ViewGroup container = view.findViewById(containerId);
|
||||
buildStatisticChart(view, chartId, routeStatistics);
|
||||
attachLegend(app, inflater, container, routeStatistics);
|
||||
}
|
||||
|
||||
private void makeGpx() {
|
||||
gpx = GPXUtilities.makeGpxFromRoute(helper.getRoute());
|
||||
String groupName = getMyApplication().getString(R.string.current_route);
|
||||
|
|
Loading…
Reference in a new issue