Merge pull request #11105 from osmandapp/track_dependent_coloring

Track coloring based on speed / altitude / slope
This commit is contained in:
Vitaliy 2021-03-15 16:32:18 +02:00 committed by GitHub
commit 5f9047cc6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 687 additions and 199 deletions

View file

@ -6,6 +6,7 @@ import net.osmand.binary.StringBundle;
import net.osmand.binary.StringBundleWriter;
import net.osmand.binary.StringBundleXmlWriter;
import net.osmand.data.QuadRect;
import net.osmand.router.RouteColorize.ColorizationType;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
@ -227,6 +228,9 @@ public class GPXUtilities {
public double hdop = Double.NaN;
public float heading = Float.NaN;
public boolean deleted = false;
public int speedColor = 0;
public int altitudeColor = 0;
public int slopeColor = 0;
public int colourARGB = 0; // point colour (used for altitude/speed colouring)
public double distance = 0.0; // cumulative distance, if in a track
@ -249,6 +253,9 @@ public class GPXUtilities {
this.hdop = wptPt.hdop;
this.heading = wptPt.heading;
this.deleted = wptPt.deleted;
this.speedColor = wptPt.speedColor;
this.altitudeColor = wptPt.altitudeColor;
this.slopeColor = wptPt.slopeColor;
this.colourARGB = wptPt.colourARGB;
this.distance = wptPt.distance;
}
@ -311,6 +318,16 @@ public class GPXUtilities {
getExtensionsToWrite().put(ICON_NAME_EXTENSION, iconName);
}
public int getColor(ColorizationType type) {
if (type == ColorizationType.SPEED) {
return speedColor;
} else if (type == ColorizationType.ELEVATION) {
return altitudeColor;
} else {
return slopeColor;
}
}
public String getBackgroundType() {
return getExtensionsToRead().get(BACKGROUND_TYPE_EXTENSION);
}
@ -1662,16 +1679,16 @@ public class GPXUtilities {
return new QuadRect(left, top, right, bottom);
}
public int getGradientScaleColor(String gradientScaleType, int defColor) {
public int[] getGradientScaleColor(String gradientScaleType) {
String clrValue = null;
if (extensions != null) {
clrValue = extensions.get(gradientScaleType);
}
return parseColor(clrValue, defColor);
return Algorithms.stringToGradientPalette(clrValue);
}
public void setGradientScaleColor(String gradientScaleType, int gradientScaleColor) {
getExtensionsToWrite().put(gradientScaleType, Algorithms.colorToString(gradientScaleColor));
public void setGradientScaleColor(String gradientScaleType, int[] gradientScalePalette) {
getExtensionsToWrite().put(gradientScaleType, Algorithms.gradientPaletteToString(gradientScalePalette));
}
public String getGradientScaleType() {

View file

@ -5,6 +5,7 @@ import net.osmand.PlatformUtil;
import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.OsmMapUtils;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import java.util.ArrayList;
@ -24,11 +25,12 @@ public class RouteColorize {
public static final int DARK_GREY = rgbaToDecimal(92, 92, 92, 255);
public static final int LIGHT_GREY = rgbaToDecimal(200, 200, 200, 255);
public static final int RED = rgbaToDecimal(255,1,1,255);
public static final int GREEN = rgbaToDecimal(46,185,0,191);
public static final int YELLOW = rgbaToDecimal(255,222,2,227);
public static final int GREEN = rgbaToDecimal(90, 220, 95, 255);
public static final int YELLOW = rgbaToDecimal(212, 239, 50, 255);
public static final int RED = rgbaToDecimal(243, 55, 77, 255);
public static final int[] colors = new int[] {GREEN, YELLOW, RED};
public enum ValueType {
public enum ColorizationType {
ELEVATION,
SPEED,
SLOPE,
@ -42,7 +44,7 @@ public class RouteColorize {
private final int BLUE_COLOR_INDEX = 3;//RGB
private final int ALPHA_COLOR_INDEX = 4;//RGBA
private ValueType valueType;
private ColorizationType colorizationType;
public static int SLOPE_RANGE = 150;//150 meters
private static final double MIN_DIFFERENCE_SLOPE = 0.05d;//5%
@ -73,7 +75,7 @@ public class RouteColorize {
/**
* @param type ELEVATION, SPEED, SLOPE
*/
public RouteColorize(int zoom, GPXUtilities.GPXFile gpxFile, ValueType type) {
public RouteColorize(int zoom, GPXUtilities.GPXFile gpxFile, ColorizationType type) {
if (!gpxFile.hasTrkPt()) {
LOG.warn("GPX file is not consist of track points");
@ -88,7 +90,7 @@ public class RouteColorize {
for (GPXUtilities.WptPt p : ts.points) {
latList.add(p.lat);
lonList.add(p.lon);
if (type == ValueType.SPEED) {
if (type == ColorizationType.SPEED) {
valList.add(p.speed);
} else {
valList.add(p.ele);
@ -101,14 +103,14 @@ public class RouteColorize {
latitudes = listToArray(latList);
longitudes = listToArray(lonList);
if (type == ValueType.SLOPE) {
if (type == ColorizationType.SLOPE) {
values = calculateSlopesByElevations(latitudes, longitudes, listToArray(valList), SLOPE_RANGE);
} else {
values = listToArray(valList);
}
calculateMinMaxValue();
valueType = type;
colorizationType = type;
checkPalette();
sortPalette();
}
@ -201,6 +203,17 @@ public class RouteColorize {
sortPalette();
}
public void setPalette(int[] gradientPalette) {
if (gradientPalette == null || gradientPalette.length != 3) {
return;
}
setPalette(new double[][] {
{minValue, gradientPalette[0]},
{colorizationType == ColorizationType.SLOPE ? 0 : (minValue + maxValue) / 2, gradientPalette[1]},
{maxValue, gradientPalette[2]}
});
}
private int getDefaultColor() {
return rgbaToDecimal(0, 0, 0, 0);
}
@ -282,7 +295,7 @@ public class RouteColorize {
double[][] defaultPalette = {
{minValue, GREEN},
{valueType == ValueType.SLOPE ? 0 : (minValue + maxValue) / 2, YELLOW},
{colorizationType == ColorizationType.SLOPE ? 0 : (minValue + maxValue) / 2, YELLOW},
{maxValue, RED}
};
palette = defaultPalette;

View file

@ -2,6 +2,7 @@ package net.osmand.util;
import net.osmand.IProgress;
import net.osmand.PlatformUtil;
import net.osmand.router.RouteColorize;
import net.osmand.data.LatLon;
import org.apache.commons.logging.Log;
@ -1061,4 +1062,30 @@ public class Algorithms {
}
return false;
}
public static int[] stringToGradientPalette(String str) {
if (Algorithms.isBlank(str)) {
return RouteColorize.colors;
}
String[] arr = str.split(" ");
if (arr.length != 3) {
return RouteColorize.colors;
}
int[] colors = new int[3];
try {
for (int i = 0; i < 3; i++) {
colors[i] = Algorithms.parseColor(arr[i]);
}
} catch (IllegalArgumentException e) {
return RouteColorize.colors;
}
return colors;
}
public static String gradientPaletteToString(int[] colors) {
int[] src = (colors != null && colors.length == 3) ? colors : RouteColorize.colors;
return Algorithms.colorToString(src[0]) + " " +
Algorithms.colorToString(src[1]) + " " +
Algorithms.colorToString(src[2]);
}
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient
android:angle="0"
android:startColor="@color/track_gradient_start"
android:centerColor="@color/track_gradient_center"
android:endColor="@color/track_gradient_end"
android:type="linear" />
</shape>
</item>
</selector>

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/content_padding"
android:paddingStart="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding">
<View
android:layout_width="match_parent"
android:layout_height="@dimen/pages_item_size"
android:layout_marginBottom="@dimen/content_padding_half"
android:background="@drawable/bg_track_gradient" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/min_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="?attr/main_font_color_basic"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="100 m"/>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/max_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:textColor="?attr/main_font_color_basic"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="100 m"/>
</LinearLayout>
</LinearLayout>

View file

@ -20,9 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/context_menu_padding_margin_tiny"
android:paddingBottom="@dimen/content_padding"
android:visibility="gone">
android:paddingTop="@dimen/context_menu_buttons_padding_bottom">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
@ -41,15 +39,4 @@
</LinearLayout>
<net.osmand.plus.widgets.FlowLayout
android:id="@+id/select_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/context_menu_padding_margin_tiny"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding_half" />
</LinearLayout>

View file

@ -45,7 +45,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/content_padding">
android:paddingBottom="@dimen/favorites_select_group_button_height">
<LinearLayout
android:layout_width="match_parent"

View file

@ -482,4 +482,8 @@
<color name="text_input_background_dark">#1AFFFFFF</color>
<color name="mtrl_textinput_default_box_stroke_color">#67727272</color>
<color name="track_gradient_start">#5ADC5F</color>
<color name="track_gradient_center">#D4EF32</color>
<color name="track_gradient_end">#F3374D</color>
</resources>

View file

@ -12,6 +12,9 @@
-->
<string name="select_another_colorization">Please select another type of colorization.</string>
<string name="track_has_no_speed">The track does not contain speed data.</string>
<string name="track_has_no_altitude">The track does not contain altitude data.</string>
<string name="shared_string_interval">Interval</string>
<string name="quick_action_show_hide_title">Show/hide</string>
<string name="copy_poi_name">Copy POI name</string>

View file

@ -1,8 +1,5 @@
package net.osmand.plus;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.IndexConstants;
@ -16,6 +13,9 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class GPXDatabase {
private static final int DB_VERSION = 11;
@ -176,11 +176,11 @@ public class GPXDatabase {
private File file;
private GPXTrackAnalysis analysis;
private String width;
private GradientScaleType gradientScaleType;
private int color;
private int gradientSpeedColor;
private int gradientAltitudeColor;
private int gradientSlopeColor;
private GradientScaleType gradientScaleType;
private int[] gradientSpeedPalette;
private int[] gradientAltitudePalette;
private int[] gradientSlopePalette;
private int splitType;
private double splitInterval;
private long fileLastModifiedTime;
@ -210,9 +210,9 @@ public class GPXDatabase {
width = gpxFile.getWidth(null);
showArrows = gpxFile.isShowArrows();
showStartFinish = gpxFile.isShowStartFinish();
gradientSpeedColor = gpxFile.getGradientScaleColor(GradientScaleType.SPEED.getColorTypeName(), 0);
gradientSlopeColor = gpxFile.getGradientScaleColor(GradientScaleType.SLOPE.getColorTypeName(), 0);
gradientAltitudeColor = gpxFile.getGradientScaleColor(GradientScaleType.ALTITUDE.getColorTypeName(), 0);
gradientSpeedPalette = gpxFile.getGradientScaleColor(GradientScaleType.SPEED.getColorTypeName());
gradientSlopePalette = gpxFile.getGradientScaleColor(GradientScaleType.SLOPE.getColorTypeName());
gradientAltitudePalette = gpxFile.getGradientScaleColor(GradientScaleType.ALTITUDE.getColorTypeName());
if (!Algorithms.isEmpty(gpxFile.getSplitType()) && gpxFile.getSplitInterval() > 0) {
GpxSplitType gpxSplitType = GpxSplitType.getSplitTypeByName(gpxFile.getSplitType());
@ -243,23 +243,22 @@ public class GPXDatabase {
return gradientScaleType;
}
public int getGradientSpeedColor() {
return gradientSpeedColor;
public int[] getGradientSpeedPalette() {
return gradientSpeedPalette;
}
public int getGradientAltitudeColor() {
return gradientAltitudeColor;
public int[] getGradientAltitudePalette() {
return gradientAltitudePalette;
}
public int getGradientSlopeColor() {
return gradientSlopeColor;
public int[] getGradientSlopePalette() {
return gradientSlopePalette;
}
public String getWidth() {
return width;
}
public long getFileLastModifiedTime() {
return fileLastModifiedTime;
}
@ -507,7 +506,7 @@ public class GPXDatabase {
return false;
}
public boolean updateGradientScaleColor(@NonNull GpxDataItem item, @NonNull GradientScaleType gradientScaleType, int gradientScaleColor) {
public boolean updateGradientScaleColor(@NonNull GpxDataItem item, @NonNull GradientScaleType gradientScaleType, int[] gradientScalePalette) {
SQLiteConnection db = openConnection(false);
if (db != null) {
try {
@ -516,17 +515,17 @@ public class GPXDatabase {
String columnName = null;
if (GradientScaleType.SPEED == gradientScaleType) {
columnName = GPX_COL_GRADIENT_SPEED_COLOR;
item.gradientSpeedColor = gradientScaleColor;
item.gradientSpeedPalette = gradientScalePalette;
} else if (GradientScaleType.ALTITUDE == gradientScaleType) {
columnName = GPX_COL_GRADIENT_ALTITUDE_COLOR;
item.gradientAltitudeColor = gradientScaleColor;
item.gradientAltitudePalette = gradientScalePalette;
} else if (GradientScaleType.SLOPE == gradientScaleType) {
columnName = GPX_COL_GRADIENT_SLOPE_COLOR;
item.gradientSlopeColor = gradientScaleColor;
item.gradientSlopePalette = gradientScalePalette;
}
db.execSQL("UPDATE " + GPX_TABLE_NAME + " SET " + columnName + " = ? " +
" WHERE " + GPX_COL_NAME + " = ? AND " + GPX_COL_DIR + " = ?",
new Object[] {(gradientScaleColor == 0 ? "" : Algorithms.colorToString(gradientScaleColor)), fileName, fileDir});
new Object[] {Algorithms.gradientPaletteToString(gradientScalePalette), fileName, fileDir});
} finally {
db.close();
}
@ -729,7 +728,7 @@ public class GPXDatabase {
color, item.file.lastModified(), item.splitType, item.splitInterval, item.apiImported ? 1 : 0,
Algorithms.encodeStringSet(item.analysis.wptCategoryNames), item.showAsMarkers ? 1 : 0,
item.joinSegments ? 1 : 0, item.showArrows ? 1 : 0, item.showStartFinish ? 1 : 0, item.width,
item.gradientSpeedColor, item.gradientAltitudeColor, item.gradientSlopeColor, gradientScaleType});
item.gradientSpeedPalette, item.gradientAltitudePalette, item.gradientSlopePalette, gradientScaleType});
} else {
db.execSQL("INSERT INTO " + GPX_TABLE_NAME + "(" +
GPX_COL_NAME + ", " +
@ -752,7 +751,9 @@ public class GPXDatabase {
new Object[] {fileName, fileDir, color, 0, item.splitType, item.splitInterval,
item.apiImported ? 1 : 0, item.showAsMarkers ? 1 : 0, item.joinSegments ? 1 : 0,
item.showArrows ? 1 : 0, item.showStartFinish ? 1 : 0, item.width,
item.gradientSpeedColor, item.gradientAltitudeColor, item.gradientSlopeColor, gradientScaleType});
Algorithms.gradientPaletteToString(item.gradientSpeedPalette),
Algorithms.gradientPaletteToString(item.gradientAltitudePalette),
Algorithms.gradientPaletteToString(item.gradientSlopePalette), gradientScaleType});
}
}
@ -836,9 +837,9 @@ public class GPXDatabase {
boolean showArrows = query.getInt(26) == 1;
boolean showStartFinish = query.getInt(27) == 1;
String width = query.getString(28);
String gradientSpeedColor = query.getString(29);
String gradientAltitudeColor = query.getString(30);
String gradientSlopeColor = query.getString(31);
String gradientSpeedPalette = query.getString(29);
String gradientAltitudePalette = query.getString(30);
String gradientSlopePalette = query.getString(31);
String gradientScaleType = query.getString(32);
GPXTrackAnalysis a = new GPXTrackAnalysis();
@ -880,12 +881,12 @@ public class GPXDatabase {
item.showArrows = showArrows;
item.showStartFinish = showStartFinish;
item.width = width;
item.gradientSpeedColor = parseColor(gradientSpeedColor);
item.gradientAltitudeColor = parseColor(gradientAltitudeColor);
item.gradientSlopeColor = parseColor(gradientSlopeColor);
item.gradientSpeedPalette = Algorithms.stringToGradientPalette(gradientSpeedPalette);
item.gradientAltitudePalette = Algorithms.stringToGradientPalette(gradientAltitudePalette);
item.gradientSlopePalette = Algorithms.stringToGradientPalette(gradientSlopePalette);
try {
item.gradientScaleType = Algorithms.isEmpty(gradientScaleType) ? null : GradientScaleType.valueOf(gradientScaleType);
item.gradientScaleType = GradientScaleType.getGradientTypeByName(gradientScaleType);
} catch (IllegalArgumentException e) {
item.gradientScaleType = null;
}

View file

@ -78,8 +78,8 @@ public class GpxDbHelper {
return res;
}
public boolean updateGradientScaleColor(@NonNull GpxDataItem item, @NonNull GradientScaleType gradientScaleType, int color) {
boolean res = db.updateGradientScaleColor(item, gradientScaleType, color);
public boolean updateGradientScalePalette(@NonNull GpxDataItem item, @NonNull GradientScaleType gradientScaleType, int[] palette) {
boolean res = db.updateGradientScaleColor(item, gradientScaleType, palette);
putToCache(item);
return res;
}

View file

@ -2310,15 +2310,9 @@ public class GpxUiHelper {
if (dataItem.getWidth() != null) {
gpxFile.setWidth(dataItem.getWidth());
}
if (dataItem.getGradientSpeedColor() != 0) {
gpxFile.setGradientScaleColor(GradientScaleType.SPEED.getColorTypeName(), dataItem.getGradientSpeedColor());
}
if (dataItem.getGradientSlopeColor() != 0) {
gpxFile.setGradientScaleColor(GradientScaleType.SLOPE.getColorTypeName(), dataItem.getGradientSlopeColor());
}
if (dataItem.getGradientAltitudeColor() != 0) {
gpxFile.setGradientScaleColor(GradientScaleType.ALTITUDE.getColorTypeName(), dataItem.getGradientAltitudeColor());
}
gpxFile.setGradientScaleColor(GradientScaleType.SPEED.getColorTypeName(), dataItem.getGradientSpeedPalette());
gpxFile.setGradientScaleColor(GradientScaleType.SLOPE.getColorTypeName(), dataItem.getGradientSlopePalette());
gpxFile.setGradientScaleColor(GradientScaleType.ALTITUDE.getColorTypeName(), dataItem.getGradientAltitudePalette());
if (dataItem.getGradientScaleType() != null) {
gpxFile.setGradientScaleType(dataItem.getGradientScaleType().name());
}

View file

@ -12,7 +12,8 @@ public class EnumStringPreference<E extends Enum<E>> extends CommonPreference<E>
@Override
protected E getValue(Object prefs, E defaultValue) {
try {
String name = getSettingsAPI().getString(prefs, getId(), defaultValue.name());
String defaultValueName = defaultValue == null ? null : defaultValue.name();
String name = getSettingsAPI().getString(prefs, getId(), defaultValueName);
E value = parseString(name);
return value != null ? value : defaultValue;
} catch (ClassCastException ex) {
@ -23,7 +24,8 @@ public class EnumStringPreference<E extends Enum<E>> extends CommonPreference<E>
@Override
public boolean setValue(Object prefs, E val) {
return getSettingsAPI().edit(prefs).putString(getId(), val.name()).commit();
String name = val == null ? null : val.name();
return getSettingsAPI().edit(prefs).putString(getId(), name).commit();
}
@Override

View file

@ -50,6 +50,7 @@ import net.osmand.plus.rastermaps.LayerTransparencySeekbarMode;
import net.osmand.plus.render.RendererRegistry;
import net.osmand.plus.routing.RouteService;
import net.osmand.plus.srtmplugin.TerrainMode;
import net.osmand.plus.track.GradientScaleType;
import net.osmand.plus.views.layers.RadiusRulerControlLayer.RadiusRulerMode;
import net.osmand.plus.voice.CommandPlayer;
import net.osmand.plus.wikipedia.WikiArticleShowImages;
@ -1408,6 +1409,10 @@ public class OsmandSettings {
public final OsmandPreference<Long> LAST_UPDATES_CARD_REFRESH = new LongPreference(this, "last_updates_card_refresh", 0).makeGlobal();
public final CommonPreference<Integer> CURRENT_TRACK_COLOR = new IntPreference(this, "current_track_color", 0).makeGlobal().makeShared().cache();
public final CommonPreference<GradientScaleType> CURRENT_TRACK_COLORIZATION = new EnumStringPreference<>(this, "current_track_colorization", null, GradientScaleType.values()).makeGlobal().makeShared().cache();
public final CommonPreference<String> CURRENT_TRACK_SPEED_GRADIENT_PALETTE = new StringPreference(this, "current_track_speed_gradient_palette", null).makeGlobal().makeShared().cache();
public final CommonPreference<String> CURRENT_TRACK_ALTITUDE_GRADIENT_PALETTE = new StringPreference(this, "current_track_altitude_gradient_palette", null).makeGlobal().makeShared().cache();
public final CommonPreference<String> CURRENT_TRACK_SLOPE_GRADIENT_PALETTE = new StringPreference(this, "current_track_slope_gradient_palette", null).makeGlobal().makeShared().cache();
public final CommonPreference<String> CURRENT_TRACK_WIDTH = new StringPreference(this, "current_track_width", "").makeGlobal().makeShared().cache();
public final CommonPreference<Boolean> CURRENT_TRACK_SHOW_ARROWS = new BooleanPreference(this, "current_track_show_arrows", false).makeGlobal().makeShared().cache();
public final CommonPreference<Boolean> CURRENT_TRACK_SHOW_START_FINISH = new BooleanPreference(this, "current_track_show_start_finish", true).makeGlobal().makeShared().cache();

View file

@ -17,9 +17,9 @@ public class GpxAppearanceInfo {
public String width;
public GradientScaleType scaleType;
public int color;
public int gradientSpeedColor;
public int gradientAltitudeColor;
public int gradientSlopeColor;
public int[] gradientSpeedPalette;
public int[] gradientAltitudePalette;
public int[] gradientSlopePalette;
public int splitType;
public double splitInterval;
public boolean showArrows;
@ -41,9 +41,9 @@ public class GpxAppearanceInfo {
splitType = dataItem.getSplitType();
splitInterval = dataItem.getSplitInterval();
scaleType = dataItem.getGradientScaleType();
gradientSpeedColor = dataItem.getGradientSpeedColor();
gradientSlopeColor = dataItem.getGradientSlopeColor();
gradientAltitudeColor = dataItem.getGradientAltitudeColor();
gradientSpeedPalette = dataItem.getGradientSpeedPalette();
gradientSlopePalette = dataItem.getGradientSlopePalette();
gradientAltitudePalette = dataItem.getGradientAltitudePalette();
GPXTrackAnalysis analysis = dataItem.getAnalysis();
if (analysis != null) {
@ -61,9 +61,9 @@ public class GpxAppearanceInfo {
writeParam(json, "split_type", GpxSplitType.getSplitTypeByTypeId(splitType).getTypeName());
writeParam(json, "split_interval", splitInterval);
writeParam(json, "gradient_scale_type", scaleType);
writeParam(json, GradientScaleType.SPEED.getColorTypeName(), gradientSpeedColor);
writeParam(json, GradientScaleType.SLOPE.getColorTypeName(), gradientSlopeColor);
writeParam(json, GradientScaleType.ALTITUDE.getColorTypeName(), gradientAltitudeColor);
writeParam(json, GradientScaleType.SPEED.getColorTypeName(), Algorithms.gradientPaletteToString(gradientSpeedPalette));
writeParam(json, GradientScaleType.ALTITUDE.getColorTypeName(), Algorithms.gradientPaletteToString(gradientAltitudePalette));
writeParam(json, GradientScaleType.SLOPE.getColorTypeName(), Algorithms.gradientPaletteToString(gradientSlopePalette));
writeParam(json, "time_span", timeSpan);
writeParam(json, "wpt_points", wptPoints);
@ -79,9 +79,9 @@ public class GpxAppearanceInfo {
gpxAppearanceInfo.splitType = GpxSplitType.getSplitTypeByName(json.optString("split_type")).getType();
gpxAppearanceInfo.splitInterval = json.optDouble("split_interval");
gpxAppearanceInfo.scaleType = getScaleType(json.optString("gradient_scale_type"));
gpxAppearanceInfo.gradientSpeedColor = json.optInt(GradientScaleType.SPEED.getColorTypeName());
gpxAppearanceInfo.gradientSlopeColor = json.optInt(GradientScaleType.SLOPE.getColorTypeName());
gpxAppearanceInfo.gradientAltitudeColor = json.optInt(GradientScaleType.ALTITUDE.getColorTypeName());
gpxAppearanceInfo.gradientSpeedPalette = getGradientPalette(json, GradientScaleType.SPEED);
gpxAppearanceInfo.gradientAltitudePalette = getGradientPalette(json, GradientScaleType.ALTITUDE);
gpxAppearanceInfo.gradientSlopePalette = getGradientPalette(json, GradientScaleType.SLOPE);
gpxAppearanceInfo.timeSpan = json.optLong("time_span");
gpxAppearanceInfo.wptPoints = json.optInt("wpt_points");
@ -101,6 +101,10 @@ public class GpxAppearanceInfo {
return null;
}
private static int[] getGradientPalette(JSONObject json, GradientScaleType scaleType) {
return Algorithms.stringToGradientPalette(json.optString(scaleType.getColorTypeName()));
}
private static void writeParam(@NonNull JSONObject json, @NonNull String name, @Nullable Object value) throws JSONException {
if (value instanceof Integer) {
if ((Integer) value != 0) {

View file

@ -9,6 +9,7 @@ import net.osmand.plus.GpxDbHelper;
import net.osmand.plus.GpxDbHelper.GpxDataItemCallback;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.track.GpxSplitType;
import net.osmand.plus.track.GradientScaleType;
import org.json.JSONException;
import org.json.JSONObject;
@ -83,6 +84,9 @@ public class GpxSettingsItem extends FileSettingsItem {
gpxDbHelper.updateShowStartFinish(dataItem, appearanceInfo.showStartFinish);
gpxDbHelper.updateSplit(dataItem, splitType, appearanceInfo.splitInterval);
gpxDbHelper.updateGradientScaleType(dataItem, appearanceInfo.scaleType);
gpxDbHelper.updateGradientScalePalette(dataItem, GradientScaleType.SPEED, appearanceInfo.gradientSpeedPalette);
gpxDbHelper.updateGradientScalePalette(dataItem, GradientScaleType.ALTITUDE, appearanceInfo.gradientAltitudePalette);
gpxDbHelper.updateGradientScalePalette(dataItem, GradientScaleType.SLOPE, appearanceInfo.gradientSlopePalette);
}
private void createGpxAppearanceInfo() {

View file

@ -0,0 +1,84 @@
package net.osmand.plus.track;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.widget.TextView;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class GradientCard extends BaseCard {
private GPXTrackAnalysis gpxTrackAnalysis;
private GradientScaleType selectedScaleType;
public GradientCard(@NonNull MapActivity mapActivity, @NonNull GPXTrackAnalysis gpxTrackAnalysis, @Nullable GradientScaleType selectedScaleType) {
super(mapActivity);
this.gpxTrackAnalysis = gpxTrackAnalysis;
this.selectedScaleType = selectedScaleType;
}
@Override
public int getCardLayoutId() {
return R.layout.gradient_card;
}
@Override
protected void updateContent() {
if (selectedScaleType == null) {
AndroidUiHelper.updateVisibility(view, false);
return;
}
AndroidUiHelper.updateVisibility(view, true);
TextView minValue = view.findViewById(R.id.min_value);
TextView maxValue = view.findViewById(R.id.max_value);
float min = getMinValue();
float max = getMaxValue(min);
minValue.setText(formatValue(min));
maxValue.setText(formatValue(max));
}
public void setSelectedScaleType(GradientScaleType type) {
this.selectedScaleType = type;
updateContent();
}
private float getMinValue() {
return (float) (selectedScaleType == GradientScaleType.ALTITUDE ? gpxTrackAnalysis.minElevation : 0.0);
}
private float getMaxValue(float minValue) {
if (selectedScaleType == GradientScaleType.SPEED) {
return (Math.max(gpxTrackAnalysis.maxSpeed, app.getSettings().getApplicationMode().getMaxSpeed()));
} else if (selectedScaleType == GradientScaleType.ALTITUDE) {
return (float) Math.max(gpxTrackAnalysis.maxElevation, minValue + 50);
} else {
return 25;
}
}
private CharSequence formatValue(float value) {
if (selectedScaleType == GradientScaleType.ALTITUDE) {
return OsmAndFormatter.getFormattedAlt(value, app);
} else if (selectedScaleType == GradientScaleType.SLOPE) {
return (int) value + " %";
}
String speed = OsmAndFormatter.getFormattedSpeed(value, app);
String speedUnit = app.getSettings().SPEED_SYSTEM.get().toShortString(app);
Spannable formattedSpeed = new SpannableString(speed);
formattedSpeed.setSpan(
new ForegroundColorSpan(AndroidUtils.getColorFromAttr(app, android.R.attr.textColorSecondary)),
speed.indexOf(speedUnit), speed.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return formattedSpeed;
}
}

View file

@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import net.osmand.plus.R;
import net.osmand.router.RouteColorize.ColorizationType;
public enum GradientScaleType {
@ -44,6 +45,18 @@ public enum GradientScaleType {
return ctx.getString(resId);
}
public ColorizationType toColorizationType() {
if (this == SPEED) {
return ColorizationType.SPEED;
} else if (this == ALTITUDE) {
return ColorizationType.ELEVATION;
} else if (this == SLOPE) {
return ColorizationType.SLOPE;
} else {
return ColorizationType.NONE;
}
}
public static GradientScaleType getGradientTypeByName(@NonNull String name) {
for (GradientScaleType scaleType : GradientScaleType.values()) {
if (scaleType.name().equalsIgnoreCase(name)) {

View file

@ -15,14 +15,6 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.PlatformUtil;
@ -60,6 +52,14 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_COLOR_ATTR;
import static net.osmand.plus.dialogs.GpxAppearanceAdapter.TRACK_WIDTH_BOLD;
import static net.osmand.plus.dialogs.GpxAppearanceAdapter.TRACK_WIDTH_MEDIUM;
@ -89,6 +89,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
private SplitIntervalCard splitIntervalCard;
private TrackColoringCard trackColoringCard;
private ColorsCard colorsCard;
private GradientCard gradientCard;
private boolean showStartFinishIconsInitialValue;
private ImageView trackIcon;
@ -158,6 +159,10 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
if (selectedGpxFile.isShowCurrentTrack()) {
trackDrawInfo = new TrackDrawInfo(true);
trackDrawInfo.setColor(app.getSettings().CURRENT_TRACK_COLOR.get());
trackDrawInfo.setGradientScaleType(app.getSettings().CURRENT_TRACK_COLORIZATION.get());
trackDrawInfo.setSpeedGradientPalette(Algorithms.stringToArray(app.getSettings().CURRENT_TRACK_SPEED_GRADIENT_PALETTE.get()));
trackDrawInfo.setAltitudeGradientPalette(Algorithms.stringToArray(app.getSettings().CURRENT_TRACK_ALTITUDE_GRADIENT_PALETTE.get()));
trackDrawInfo.setSlopeGradientPalette(Algorithms.stringToArray(app.getSettings().CURRENT_TRACK_SLOPE_GRADIENT_PALETTE.get()));
trackDrawInfo.setWidth(app.getSettings().CURRENT_TRACK_WIDTH.get());
trackDrawInfo.setShowArrows(app.getSettings().CURRENT_TRACK_SHOW_ARROWS.get());
trackDrawInfo.setShowStartFinish(app.getSettings().CURRENT_TRACK_SHOW_START_FINISH.get());
@ -340,6 +345,16 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
if (mapActivity != null) {
if (card instanceof SplitIntervalCard) {
SplitIntervalBottomSheet.showInstance(mapActivity.getSupportFragmentManager(), trackDrawInfo, this);
} else if (card instanceof TrackColoringCard) {
GradientScaleType currentScaleType = ((TrackColoringCard) card).getSelectedScaleType();
trackDrawInfo.setGradientScaleType(currentScaleType);
mapActivity.refreshMap();
if (gradientCard != null) {
gradientCard.setSelectedScaleType(currentScaleType);
}
if (colorsCard != null) {
AndroidUiHelper.updateVisibility(colorsCard.getView(), currentScaleType == null);
}
} else if (card instanceof ColorsCard) {
int color = ((ColorsCard) card).getSelectedColor();
trackDrawInfo.setColor(color);
@ -555,6 +570,9 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
if (trackWidthCard != null) {
trackWidthCard.updateItems();
}
if (trackColoringCard != null) {
trackColoringCard.updateColor();
}
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.refreshMap();
@ -565,12 +583,19 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
GPXFile gpxFile = selectedGpxFile.getGpxFile();
if (gpxFile.showCurrentTrack) {
app.getSettings().CURRENT_TRACK_COLOR.set(trackDrawInfo.getColor());
app.getSettings().CURRENT_TRACK_COLORIZATION.set(trackDrawInfo.getGradientScaleType());
app.getSettings().CURRENT_TRACK_SPEED_GRADIENT_PALETTE.set(Algorithms.arrayToString(trackDrawInfo.getSpeedGradientPalette()));
app.getSettings().CURRENT_TRACK_ALTITUDE_GRADIENT_PALETTE.set(Algorithms.arrayToString(trackDrawInfo.getAltitudeGradientPalette()));
app.getSettings().CURRENT_TRACK_SLOPE_GRADIENT_PALETTE.set(Algorithms.arrayToString(trackDrawInfo.getSlopeGradientPalette()));
app.getSettings().CURRENT_TRACK_WIDTH.set(trackDrawInfo.getWidth());
app.getSettings().CURRENT_TRACK_SHOW_ARROWS.set(trackDrawInfo.isShowArrows());
app.getSettings().CURRENT_TRACK_SHOW_START_FINISH.set(trackDrawInfo.isShowStartFinish());
} else if (gpxDataItem != null) {
GpxSplitType splitType = GpxSplitType.getSplitTypeByTypeId(trackDrawInfo.getSplitType());
gpxDbHelper.updateColor(gpxDataItem, trackDrawInfo.getColor());
gpxDbHelper.updateGradientScalePalette(gpxDataItem, GradientScaleType.SPEED, trackDrawInfo.getSpeedGradientPalette());
gpxDbHelper.updateGradientScalePalette(gpxDataItem, GradientScaleType.ALTITUDE, trackDrawInfo.getAltitudeGradientPalette());
gpxDbHelper.updateGradientScalePalette(gpxDataItem, GradientScaleType.SLOPE, trackDrawInfo.getSlopeGradientPalette());
gpxDbHelper.updateWidth(gpxDataItem, trackDrawInfo.getWidth());
gpxDbHelper.updateShowArrows(gpxDataItem, trackDrawInfo.isShowArrows());
// gpxDbHelper.updateShowStartFinish(gpxDataItem, trackDrawInfo.isShowStartFinish());
@ -642,12 +667,15 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
showStartFinishCard.setListener(this);
cardsContainer.addView(showStartFinishCard.build(mapActivity));
trackColoringCard = new TrackColoringCard(mapActivity, trackDrawInfo, this);
trackColoringCard = new TrackColoringCard(mapActivity, selectedGpxFile.getTrackAnalysis(app), trackDrawInfo);
trackColoringCard.setListener(this);
cardsContainer.addView(trackColoringCard.build(mapActivity));
setupColorsCard(cardsContainer);
gradientCard = new GradientCard(mapActivity, selectedGpxFile.getTrackAnalysis(app), trackDrawInfo.getGradientScaleType());
cardsContainer.addView(gradientCard.build(mapActivity));
trackWidthCard = new TrackWidthCard(mapActivity, trackDrawInfo, new OnNeedScrollListener() {
@Override
@ -674,7 +702,8 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
List<Integer> colors = getTrackColors();
colorsCard = new ColorsCard(mapActivity, trackDrawInfo.getColor(), this, colors, app.getSettings().CUSTOM_TRACK_COLORS, null);
colorsCard.setListener(this);
cardsContainer.addView(colorsCard.build(mapActivity));
AndroidUiHelper.updateVisibility(colorsCard.build(mapActivity), trackDrawInfo.getGradientScaleType() == null);
cardsContainer.addView(colorsCard.getView());
}
}

View file

@ -1,5 +1,6 @@
package net.osmand.plus.track;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.view.LayoutInflater;
@ -7,14 +8,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.PlatformUtil;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
@ -27,26 +24,29 @@ import org.apache.commons.logging.Log;
import java.util.ArrayList;
import java.util.List;
public class TrackColoringCard extends BaseCard {
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
private static final int MINIMUM_CONTRAST_RATIO = 3;
public class TrackColoringCard extends BaseCard {
private final static String SOLID_COLOR = "solid_color";
private static final Log log = PlatformUtil.getLog(TrackColoringCard.class);
private GPXTrackAnalysis gpxTrackAnalysis;
private TrackDrawInfo trackDrawInfo;
private TrackColoringAdapter coloringAdapter;
private TrackAppearanceItem selectedAppearanceItem;
private List<TrackAppearanceItem> appearanceItems;
private Fragment target;
public TrackColoringCard(MapActivity mapActivity, TrackDrawInfo trackDrawInfo, Fragment target) {
public TrackColoringCard(MapActivity mapActivity, GPXTrackAnalysis gpxTrackAnalysis, TrackDrawInfo trackDrawInfo) {
super(mapActivity);
this.target = target;
this.trackDrawInfo = trackDrawInfo;
appearanceItems = getGradientAppearanceItems();
this.gpxTrackAnalysis = gpxTrackAnalysis;
appearanceItems = getTrackAppearanceItems();
}
@Override
@ -58,25 +58,44 @@ public class TrackColoringCard extends BaseCard {
protected void updateContent() {
updateHeader();
// coloringAdapter = new TrackColoringAdapter(appearanceItems);
// RecyclerView groupRecyclerView = view.findViewById(R.id.recycler_view);
// groupRecyclerView.setAdapter(coloringAdapter);
// groupRecyclerView.setLayoutManager(new LinearLayoutManager(app, RecyclerView.HORIZONTAL, false));
coloringAdapter = new TrackColoringAdapter(appearanceItems);
RecyclerView groupRecyclerView = view.findViewById(R.id.recycler_view);
groupRecyclerView.setLayoutManager(new LinearLayoutManager(app, RecyclerView.HORIZONTAL, false));
groupRecyclerView.setAdapter(coloringAdapter);
AndroidUiHelper.updateVisibility(view.findViewById(R.id.top_divider), isShowDivider());
}
private List<TrackAppearanceItem> getGradientAppearanceItems() {
public void updateColor() {
if (coloringAdapter != null) {
// Provide empty object to update item without animation
coloringAdapter.notifyItemChanged(0, new Object());
}
}
public GradientScaleType getSelectedScaleType() {
String attrName = selectedAppearanceItem.getAttrName();
return attrName.equals(SOLID_COLOR) ? null : GradientScaleType.valueOf(attrName.toUpperCase());
}
private List<TrackAppearanceItem> getTrackAppearanceItems() {
List<TrackAppearanceItem> items = new ArrayList<>();
items.add(new TrackAppearanceItem(SOLID_COLOR, app.getString(R.string.track_coloring_solid), R.drawable.ic_action_circle));
// for (GradientScaleType scaleType : GradientScaleType.values()) {
// items.add(new TrackAppearanceItem(scaleType.getTypeName(), scaleType.getHumanString(app), scaleType.getIconId()));
// }
items.add(new TrackAppearanceItem(SOLID_COLOR, app.getString(R.string.track_coloring_solid), R.drawable.ic_action_circle, true));
for (GradientScaleType scaleType : GradientScaleType.values()) {
items.add(new TrackAppearanceItem(scaleType.getTypeName(),
scaleType.getHumanString(app), scaleType.getIconId(), isScaleTypeActive(scaleType)));
}
return items;
}
private boolean isScaleTypeActive(GradientScaleType scaleType) {
if (scaleType == GradientScaleType.SPEED) {
return gpxTrackAnalysis.isSpeedSpecified();
} else {
return gpxTrackAnalysis.isElevationSpecified();
}
}
private TrackAppearanceItem getSelectedAppearanceItem() {
if (selectedAppearanceItem == null) {
GradientScaleType scaleType = trackDrawInfo.getGradientScaleType();
@ -98,27 +117,22 @@ public class TrackColoringCard extends BaseCard {
headerView.setBackgroundDrawable(null);
TextView titleView = view.findViewById(R.id.title);
titleView.setText(R.string.select_color);
titleView.setText(R.string.shared_string_color);
TextView descriptionView = view.findViewById(R.id.description);
descriptionView.setText(getSelectedAppearanceItem().getLocalizedValue());
}
private void updateColorSelector() {
boolean visible = getSelectedAppearanceItem().getAttrName().equals(SOLID_COLOR);
AndroidUiHelper.updateVisibility(view.findViewById(R.id.select_color), visible);
}
public void setGradientScaleType(TrackAppearanceItem item) {
selectedAppearanceItem = item;
if (item.getAttrName().equals(SOLID_COLOR)) {
trackDrawInfo.setGradientScaleType(null);
} else {
trackDrawInfo.setGradientScaleType(GradientScaleType.valueOf(item.getAttrName()));
trackDrawInfo.setGradientScaleType(GradientScaleType.valueOf(item.getAttrName().toUpperCase()));
}
mapActivity.refreshMap();
updateHeader();
updateColorSelector();
}
private class TrackColoringAdapter extends RecyclerView.Adapter<TrackAppearanceViewHolder> {
@ -136,42 +150,38 @@ public class TrackColoringCard extends BaseCard {
View view = themedInflater.inflate(R.layout.point_editor_group_select_item, parent, false);
view.getLayoutParams().width = app.getResources().getDimensionPixelSize(R.dimen.gpx_group_button_width);
view.getLayoutParams().height = app.getResources().getDimensionPixelSize(R.dimen.gpx_group_button_height);
TrackAppearanceViewHolder holder = new TrackAppearanceViewHolder(view);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AndroidUtils.setBackground(app, holder.button, nightMode, R.drawable.ripple_solid_light_6dp,
R.drawable.ripple_solid_dark_6dp);
}
return holder;
return new TrackAppearanceViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final TrackAppearanceViewHolder holder, int position) {
TrackAppearanceItem item = items.get(position);
holder.title.setText(item.getLocalizedValue());
final TrackAppearanceItem item = items.get(position);
updateButtonBg(holder, item);
int colorId;
if (item.getAttrName().equals(SOLID_COLOR)) {
colorId = trackDrawInfo.getColor();
} else if (item.getAttrName().equals(getSelectedAppearanceItem().getAttrName())) {
colorId = ContextCompat.getColor(app, nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light);
} else {
colorId = AndroidUtils.getColorFromAttr(holder.itemView.getContext(), R.attr.default_icon_color);
if (item.isActive() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AndroidUtils.setBackground(app, holder.button, nightMode, R.drawable.ripple_solid_light_6dp,
R.drawable.ripple_solid_dark_6dp);
}
holder.icon.setImageDrawable(app.getUIUtilities().getPaintedIcon(item.getIconId(), colorId));
updateButtonBg(holder, item);
updateTextAndIconColor(holder, item);
holder.title.setText(item.getLocalizedValue());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!item.isActive()) {
showSnackbar(view, item.getAttrName());
return;
}
int prevSelectedPosition = getItemPosition(getSelectedAppearanceItem());
selectedAppearanceItem = items.get(holder.getAdapterPosition());
notifyItemChanged(holder.getAdapterPosition());
notifyItemChanged(prevSelectedPosition);
setGradientScaleType(selectedAppearanceItem);
if (getListener() != null) {
getListener().onCardPressed(TrackColoringCard.this);
}
}
});
}
@ -179,18 +189,72 @@ public class TrackColoringCard extends BaseCard {
private void updateButtonBg(TrackAppearanceViewHolder holder, TrackAppearanceItem item) {
GradientDrawable rectContourDrawable = (GradientDrawable) AppCompatResources.getDrawable(app, R.drawable.bg_select_group_button_outline);
if (rectContourDrawable != null) {
if (getSelectedAppearanceItem() != null && getSelectedAppearanceItem().equals(item)) {
int strokeColor = ContextCompat.getColor(app, nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light);
rectContourDrawable.setStroke(AndroidUtils.dpToPx(app, 2), strokeColor);
Context ctx = holder.itemView.getContext();
boolean itemSelected = getSelectedAppearanceItem() != null && getSelectedAppearanceItem().equals(item);
int strokeColor;
int backgroundColor;
int strokeWidth;
if (itemSelected) {
strokeColor = AndroidUtils.getColorFromAttr(ctx, R.attr.colorPrimary);
backgroundColor = 0;
strokeWidth = 2;
} else if (!item.isActive()) {
strokeColor = AndroidUtils.getColorFromAttr(ctx, R.attr.stroked_buttons_and_links_outline);
backgroundColor = AndroidUtils.getColorFromAttr(ctx, R.attr.ctx_menu_card_btn);
strokeWidth = 2;
} else {
int strokeColor = ContextCompat.getColor(app, nightMode ? R.color.stroked_buttons_and_links_outline_dark
: R.color.stroked_buttons_and_links_outline_light);
rectContourDrawable.setStroke(AndroidUtils.dpToPx(app, 1), strokeColor);
strokeColor = AndroidUtils.getColorFromAttr(ctx, R.attr.stroked_buttons_and_links_outline);
backgroundColor = 0;
strokeWidth = 1;
}
rectContourDrawable.mutate();
rectContourDrawable.setColor(backgroundColor);
rectContourDrawable.setStroke(AndroidUtils.dpToPx(ctx, strokeWidth), strokeColor);
holder.button.setImageDrawable(rectContourDrawable);
}
}
private void updateTextAndIconColor(TrackAppearanceViewHolder holder, TrackAppearanceItem item) {
Context ctx = holder.itemView.getContext();
boolean isSelected = item.getAttrName().equals(getSelectedAppearanceItem().getAttrName());
int iconColorId;
int textColorId;
if (isSelected) {
iconColorId = AndroidUtils.getColorFromAttr(ctx, R.attr.default_icon_color);
textColorId = AndroidUtils.getColorFromAttr(ctx, android.R.attr.textColor);
} else if (!item.isActive()) {
iconColorId = AndroidUtils.getColorFromAttr(ctx, R.attr.default_icon_color);
textColorId = AndroidUtils.getColorFromAttr(ctx, android.R.attr.textColorSecondary);
} else {
iconColorId = AndroidUtils.getColorFromAttr(ctx, R.attr.colorPrimary);
textColorId = iconColorId;
}
if (item.getAttrName().equals(SOLID_COLOR)) {
iconColorId = trackDrawInfo.getColor();
}
holder.icon.setImageDrawable(app.getUIUtilities().getPaintedIcon(item.getIconId(), iconColorId));
holder.title.setTextColor(textColorId);
}
private void showSnackbar(View view, String attrName) {
if (view == null || mapActivity == null) {
return;
}
String text = attrName.equals(GradientScaleType.SPEED.getTypeName()) ?
app.getString(R.string.track_has_no_speed) : app.getString(R.string.track_has_no_altitude);
text += " " + app.getString(R.string.select_another_colorization);
Snackbar snackbar = Snackbar.make(view, text, Snackbar.LENGTH_LONG)
.setAnchorView(mapActivity.findViewById(R.id.dismiss_button));
UiUtilities.setupSnackbar(snackbar, nightMode);
snackbar.show();
}
@Override
public int getItemCount() {
return items.size();
@ -209,10 +273,13 @@ public class TrackColoringCard extends BaseCard {
@DrawableRes
private int iconId;
public TrackAppearanceItem(String attrName, String localizedValue, int iconId) {
private boolean isActive;
public TrackAppearanceItem(String attrName, String localizedValue, int iconId, boolean isActive) {
this.attrName = attrName;
this.localizedValue = localizedValue;
this.iconId = iconId;
this.isActive = isActive;
}
public String getAttrName() {
@ -226,5 +293,9 @@ public class TrackColoringCard extends BaseCard {
public int getIconId() {
return iconId;
}
public boolean isActive() {
return isActive;
}
}
}

View file

@ -26,6 +26,9 @@ public class TrackDrawInfo {
private String width;
private GradientScaleType gradientScaleType;
private int color;
private int[] speedGradientPalette;
private int[] altitudeGradientPalette;
private int[] slopeGradientPalette;
private int splitType;
private double splitInterval;
private boolean joinSegments;
@ -44,8 +47,11 @@ public class TrackDrawInfo {
public TrackDrawInfo(@NonNull OsmandApplication app, @NonNull GpxDataItem gpxDataItem, boolean currentRecording) {
filePath = gpxDataItem.getFile().getPath();
width = gpxDataItem.getWidth();
gradientScaleType = gpxDataItem.getGradientScaleType();
color = gpxDataItem.getColor();
gradientScaleType = gpxDataItem.getGradientScaleType();
speedGradientPalette = gpxDataItem.getGradientSpeedPalette();
altitudeGradientPalette = gpxDataItem.getGradientAltitudePalette();
slopeGradientPalette = gpxDataItem.getGradientSlopePalette();
splitType = gpxDataItem.getSplitType();
splitInterval = gpxDataItem.getSplitInterval();
joinSegments = gpxDataItem.isJoinSegments();
@ -82,6 +88,40 @@ public class TrackDrawInfo {
this.color = color;
}
public int[] getGradientPalette(@NonNull GradientScaleType scaleType) {
if (scaleType == GradientScaleType.SPEED) {
return speedGradientPalette;
} else if (scaleType == GradientScaleType.ALTITUDE) {
return altitudeGradientPalette;
} else {
return slopeGradientPalette;
}
}
public int[] getSpeedGradientPalette() {
return speedGradientPalette;
}
public int[] getAltitudeGradientPalette() {
return altitudeGradientPalette;
}
public int[] getSlopeGradientPalette() {
return slopeGradientPalette;
}
public void setSpeedGradientPalette(int[] palette) {
this.speedGradientPalette = palette;
}
public void setAltitudeGradientPalette(int[] palette) {
this.altitudeGradientPalette = palette;
}
public void setSlopeGradientPalette(int[] palette) {
this.slopeGradientPalette = palette;
}
public int getSplitType() {
return splitType;
}

View file

@ -1,16 +1,16 @@
package net.osmand.plus.views;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import androidx.annotation.NonNull;
import android.graphics.Shader;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.views.layers.geometry.GeometryWay;
import net.osmand.plus.track.GradientScaleType;
import net.osmand.plus.views.layers.geometry.GpxGeometryWay;
import net.osmand.util.Algorithms;
@ -25,6 +25,8 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import androidx.annotation.NonNull;
public class Renderable {
@ -65,6 +67,7 @@ public class Renderable {
protected double zoom = -1;
protected AsynchronousResampler culler = null; // The currently active resampler
protected Paint paint = null; // MUST be set by 'updateLocalPaint' before use
protected GradientScaleType scaleType = null;
protected GpxGeometryWay geometryWay;
@ -85,6 +88,10 @@ public class Renderable {
paint.setStrokeWidth(p.getStrokeWidth());
}
public void setGradientScaleType(GradientScaleType type) {
this.scaleType = type;
}
public GpxGeometryWay getGeometryWay() {
return geometryWay;
}
@ -95,7 +102,20 @@ public class Renderable {
protected abstract void startCuller(double newZoom);
protected void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {}
protected void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {
if (points.size() < 2) {
return;
}
updateLocalPaint(p);
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
if (scaleType != null) {
drawGradient(getPointsForDrawing(), p, canvas, tileBox);
} else {
drawSolid(getPointsForDrawing(), p, canvas, tileBox);
}
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
}
public void drawSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {
@ -124,42 +144,66 @@ public class Renderable {
}
}
protected void draw(List<WptPt> pts, Paint p, Canvas canvas, RotatedTileBox tileBox) {
if (pts.size() > 1) {
updateLocalPaint(p);
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
QuadRect tileBounds = tileBox.getLatLonBounds();
WptPt lastPt = pts.get(0);
boolean recalculateLastXY = true;
Path path = new Path();
for (int i = 1; i < pts.size(); i++) {
WptPt pt = pts.get(i);
if (Math.min(pt.lon, lastPt.lon) < tileBounds.right && Math.max(pt.lon, lastPt.lon) > tileBounds.left
&& Math.min(pt.lat, lastPt.lat) < tileBounds.top && Math.max(pt.lat, lastPt.lat) > tileBounds.bottom) {
if (recalculateLastXY) {
recalculateLastXY = false;
float lastX = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
float lastY = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
if (!path.isEmpty()) {
canvas.drawPath(path, paint);
}
path.reset();
path.moveTo(lastX, lastY);
protected void drawSolid(List<WptPt> pts, Paint p, Canvas canvas, RotatedTileBox tileBox) {
QuadRect tileBounds = tileBox.getLatLonBounds();
WptPt lastPt = pts.get(0);
boolean recalculateLastXY = true;
Path path = new Path();
for (int i = 1; i < pts.size(); i++) {
WptPt pt = pts.get(i);
if (arePointsInsideTile(pt, lastPt, tileBounds)) {
if (recalculateLastXY) {
recalculateLastXY = false;
float lastX = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
float lastY = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
if (!path.isEmpty()) {
canvas.drawPath(path, paint);
}
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
path.lineTo(x, y);
} else {
recalculateLastXY = true;
path.reset();
path.moveTo(lastX, lastY);
}
lastPt = pt;
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
path.lineTo(x, y);
} else {
recalculateLastXY = true;
}
if (!path.isEmpty()) {
lastPt = pt;
}
if (!path.isEmpty()) {
canvas.drawPath(path, paint);
}
}
protected void drawGradient(List<WptPt> pts, Paint p, Canvas canvas, RotatedTileBox tileBox) {
QuadRect tileBounds = tileBox.getLatLonBounds();
Path path = new Path();
Paint paint = new Paint(this.paint);
WptPt prevPt = pts.get(0);
for (int i = 1; i < pts.size(); i++) {
WptPt currentPt = pts.get(i);
if (arePointsInsideTile(currentPt, prevPt, tileBounds)) {
float startX = tileBox.getPixXFromLatLon(prevPt.lat, prevPt.lon);
float startY = tileBox.getPixYFromLatLon(prevPt.lat, prevPt.lon);
float endX = tileBox.getPixXFromLatLon(currentPt.lat, currentPt.lon);
float endY = tileBox.getPixYFromLatLon(currentPt.lat, currentPt.lon);
int prevColor = prevPt.getColor(scaleType.toColorizationType());
int currentColor = currentPt.getColor(scaleType.toColorizationType());
LinearGradient gradient = new LinearGradient(startX, startY, endX, endY, prevColor, currentColor, Shader.TileMode.CLAMP);
paint.setShader(gradient);
path.reset();
path.moveTo(startX, startY);
path.lineTo(endX, endY);
canvas.drawPath(path, paint);
}
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
prevPt = currentPt;
}
}
protected boolean arePointsInsideTile(WptPt first, WptPt second, QuadRect tileBounds) {
return Math.min(first.lon, second.lon) < tileBounds.right && Math.max(first.lon, second.lon) > tileBounds.left
&& Math.min(first.lat, second.lat) < tileBounds.top && Math.max(first.lat, second.lat) > tileBounds.bottom;
}
}
public static class StandardTrack extends RenderableSegment {
@ -191,10 +235,6 @@ public class Renderable {
}
}
}
@Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {
draw(culled.isEmpty() ? points : culled, p, canvas, tileBox);
}
}
public static class CurrentTrack extends RenderableSegment {
@ -213,9 +253,5 @@ public class Renderable {
}
@Override protected void startCuller(double newZoom) {}
@Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {
draw(points, p, canvas, tileBox);
}
}
}

View file

@ -53,6 +53,7 @@ import net.osmand.plus.render.OsmandRenderer;
import net.osmand.plus.render.OsmandRenderer.RenderingContext;
import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu;
import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.track.GradientScaleType;
import net.osmand.plus.track.SaveGpxAsyncTask;
import net.osmand.plus.track.TrackDrawInfo;
import net.osmand.plus.views.OsmandMapLayer;
@ -66,6 +67,7 @@ import net.osmand.plus.views.layers.geometry.GpxGeometryWayContext;
import net.osmand.render.RenderingRuleProperty;
import net.osmand.render.RenderingRuleSearchRequest;
import net.osmand.render.RenderingRulesStorage;
import net.osmand.router.RouteColorize;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
@ -142,6 +144,10 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
private CommonPreference<String> defaultTrackWidthPref;
private CommonPreference<Integer> currentTrackColorPref;
private CommonPreference<GradientScaleType> currentTrackScaleType;
private CommonPreference<String> currentTrackSpeedGradientPalette;
private CommonPreference<String> currentTrackAltitudeGradientPalette;
private CommonPreference<String> currentTrackSlopeGradientPalette;
private CommonPreference<String> currentTrackWidthPref;
private CommonPreference<Boolean> currentTrackShowArrowsPref;
private CommonPreference<Boolean> currentTrackShowStartFinishPref;
@ -155,6 +161,10 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
osmandRenderer = view.getApplication().getResourceManager().getRenderer().getRenderer();
currentTrackColorPref = view.getSettings().CURRENT_TRACK_COLOR;
currentTrackScaleType = view.getSettings().CURRENT_TRACK_COLORIZATION;
currentTrackSpeedGradientPalette = view.getSettings().CURRENT_TRACK_SPEED_GRADIENT_PALETTE;
currentTrackAltitudeGradientPalette = view.getSettings().CURRENT_TRACK_ALTITUDE_GRADIENT_PALETTE;
currentTrackSlopeGradientPalette = view.getSettings().CURRENT_TRACK_SLOPE_GRADIENT_PALETTE;
currentTrackWidthPref = view.getSettings().CURRENT_TRACK_WIDTH;
currentTrackShowArrowsPref = view.getSettings().CURRENT_TRACK_SHOW_ARROWS;
currentTrackShowStartFinishPref = view.getSettings().CURRENT_TRACK_SHOW_START_FINISH;
@ -661,10 +671,22 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
private void drawSelectedFileSegments(SelectedGpxFile selectedGpxFile, boolean currentTrack, Canvas canvas,
RotatedTileBox tileBox, DrawSettings settings) {
GPXFile gpxFile = selectedGpxFile.getGpxFile();
List<TrkSegment> segments = selectedGpxFile.getPointsToDisplay();
GradientScaleType scaleType = getGradientScaleType(gpxFile);
List<RouteColorize.RouteColorizationPoint> colorsOfPoints = null;
if (scaleType != null) {
RouteColorize colorize = new RouteColorize(view.getZoom(), gpxFile, scaleType.toColorizationType());
colorize.setPalette(getColorizationPalette(gpxFile, scaleType));
colorsOfPoints = colorize.getResult(false);
}
int startIdx = 0;
for (TrkSegment ts : segments) {
String width = getTrackWidthName(selectedGpxFile.getGpxFile(), defaultTrackWidthPref.get());
int color = getTrackColor(selectedGpxFile.getGpxFile(), ts.getColor(cachedColor));
String width = getTrackWidthName(gpxFile, defaultTrackWidthPref.get());
int color = getTrackColor(gpxFile, ts.getColor(cachedColor));
if (colorsOfPoints != null) {
startIdx = setColorsToPoints(ts, colorsOfPoints, scaleType, startIdx);
}
if (ts.renderer == null && !ts.points.isEmpty()) {
Renderable.RenderableSegment renderer;
if (currentTrack) {
@ -677,11 +699,43 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
}
updatePaints(color, width, selectedGpxFile.isRoutePoints(), currentTrack, settings, tileBox);
if (ts.renderer instanceof Renderable.RenderableSegment) {
((Renderable.RenderableSegment) ts.renderer).drawSegment(view.getZoom(), paint, canvas, tileBox);
Renderable.RenderableSegment renderableSegment = (Renderable.RenderableSegment) ts.renderer;
renderableSegment.setGradientScaleType(scaleType);
renderableSegment.drawSegment(view.getZoom(), paint, canvas, tileBox);
}
}
}
private int setColorsToPoints(TrkSegment segment, List<RouteColorize.RouteColorizationPoint> colors, GradientScaleType scaleType, int startIdx) {
int pointsSize = segment.points.size();
RouteColorize.RouteColorizationPoint startColor = colors.get(startIdx);
RouteColorize.RouteColorizationPoint endColor = colors.get(startIdx + pointsSize - 1);
WptPt firstPoint = segment.points.get(0);
WptPt lastPoint = segment.points.get(pointsSize - 1);
while (!compareCoordinates(firstPoint, startColor) && compareCoordinates(lastPoint, endColor)) {
startIdx++;
startColor = colors.get(startIdx);
endColor = colors.get(startIdx + pointsSize - 1);
}
for (int i = startIdx; i < startIdx + pointsSize; i++) {
WptPt currentPoint = segment.points.get(i - startIdx);
int currentColor = colors.get(i).color;
if (scaleType == GradientScaleType.SPEED) {
currentPoint.speedColor = currentColor;
} else if (scaleType == GradientScaleType.ALTITUDE) {
currentPoint.altitudeColor = currentColor;
} else {
currentPoint.slopeColor = currentColor;
}
}
return startIdx;
}
private boolean compareCoordinates(WptPt left, RouteColorize.RouteColorizationPoint right) {
return left.lat == right.lat && left.lon == right.lon;
}
private float getTrackWidth(String width, float defaultTrackWidth) {
Float trackWidth = cachedTrackWidth.get(width);
return trackWidth != null ? trackWidth : defaultTrackWidth;
@ -702,6 +756,47 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
return color != 0 ? color : defaultColor;
}
private GradientScaleType getGradientScaleType(GPXFile gpxFile) {
if (hasTrackDrawInfoForTrack(gpxFile)) {
return trackDrawInfo.getGradientScaleType();
} else if (gpxFile.showCurrentTrack) {
return currentTrackScaleType.get();
} else {
GpxDataItem dataItem = gpxDbHelper.getItem(new File(gpxFile.path));
if (dataItem != null && dataItem.getGradientScaleType() != null) {
return dataItem.getGradientScaleType();
}
}
return null;
}
private int[] getColorizationPalette(GPXFile gpxFile, GradientScaleType scaleType) {
if (hasTrackDrawInfoForTrack(gpxFile)) {
return trackDrawInfo.getGradientPalette(scaleType);
} else if (gpxFile.showCurrentTrack) {
String palette;
if (scaleType == GradientScaleType.SPEED) {
palette = currentTrackSpeedGradientPalette.get();
} else if (scaleType == GradientScaleType.ALTITUDE) {
palette = currentTrackAltitudeGradientPalette.get();
} else {
palette = currentTrackSlopeGradientPalette.get();
}
return Algorithms.stringToArray(palette);
}
GpxDataItem dataItem = gpxDbHelper.getItem(new File(gpxFile.path));
if (dataItem == null) {
return RouteColorize.colors;
}
if (scaleType == GradientScaleType.SPEED) {
return dataItem.getGradientSpeedPalette();
} else if (scaleType == GradientScaleType.ALTITUDE) {
return dataItem.getGradientAltitudePalette();
} else {
return dataItem.getGradientSlopePalette();
}
}
private String getTrackWidthName(GPXFile gpxFile, String defaultWidth) {
String width = null;
if (hasTrackDrawInfoForTrack(gpxFile)) {