Merge pull request #6228 from osmandapp/road-surface

split route by surface type
This commit is contained in:
Alexey 2019-02-06 19:35:06 +03:00 committed by GitHub
commit ed81bc438c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 902 additions and 10 deletions

View file

@ -1718,4 +1718,6 @@ public class RouteResultPreparation {
return MapUtils.getDistance(MapUtils.get31LatitudeY(y1), MapUtils.get31LongitudeX(x1),
MapUtils.get31LatitudeY(y2), MapUtils.get31LongitudeX(x2));
}
}

View file

@ -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;
}
@ -217,5 +227,16 @@ public class RouteSegmentResult {
public String toString() {
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();
}
}

View 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;
}
}
}

View file

@ -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"

View 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>

View file

@ -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>

View file

@ -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);