diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java b/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java index 6a0c5c5794..ece3b01a38 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java @@ -186,7 +186,7 @@ public class RouteColorize { public List getResult(boolean simplify) { List result = new ArrayList<>(); if (simplify) { - result = simplify(); + result = simplify(zoom); } else { for (int i = 0; i < latitudes.length; i++) { result.add(new RouteColorizationPoint(i, latitudes[i], longitudes[i], values[i])); @@ -200,7 +200,7 @@ public class RouteColorize { public int getColorByValue(double value) { if (Double.isNaN(value)) { - value = (minValue + maxValue) / 2; + value = colorizationType == ColorizationType.SLOPE ? minValue : (minValue + maxValue) / 2; } for (int i = 0; i < palette.length - 1; i++) { if (value == palette[i][VALUE_INDEX]) @@ -242,7 +242,7 @@ public class RouteColorize { return rgbaToDecimal(0, 0, 0, 0); } - private List simplify() { + public List simplify(int zoom) { if (dataList == null) { dataList = new ArrayList<>(); for (int i = 0; i < latitudes.length; i++) { @@ -266,6 +266,8 @@ public class RouteColorize { List sublist = dataList.subList(prevId, currentId); simplified.addAll(getExtremums(sublist)); } + Node lastSurvivedPoint = result.get(result.size() - 1); + simplified.add(dataList.get((int) lastSurvivedPoint.getId())); return simplified; } diff --git a/OsmAnd/res/layout/gradient_card.xml b/OsmAnd/res/layout/gradient_card.xml index a4edba9be2..11b82f10ad 100644 --- a/OsmAnd/res/layout/gradient_card.xml +++ b/OsmAnd/res/layout/gradient_card.xml @@ -5,8 +5,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:padding="@dimen/content_padding" android:paddingStart="@dimen/content_padding" + android:paddingLeft="@dimen/content_padding" + android:paddingTop="@dimen/content_padding" + android:paddingRight="@dimen/content_padding" android:paddingEnd="@dimen/content_padding"> + + \ No newline at end of file diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 3008963ea3..28204402e8 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -12,6 +12,9 @@ --> + Max. height + Min. height + Route line will be colorized depending on the elevation profile of the route. Output User points Announce when exceeded diff --git a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java index c5eca0edb1..849b8510ab 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java @@ -2125,9 +2125,12 @@ public class GpxUiHelper { public static GPXFile makeGpxFromRoute(RouteCalculationResult route, OsmandApplication app) { + return makeGpxFromLocations(route.getRouteLocations(), app); + } + + public static GPXFile makeGpxFromLocations(List locations, OsmandApplication app) { double lastHeight = HEIGHT_UNDEFINED; GPXFile gpx = new GPXUtilities.GPXFile(Version.getFullVersion(app)); - List locations = route.getRouteLocations(); if (locations != null) { GPXUtilities.Track track = new GPXUtilities.Track(); GPXUtilities.TrkSegment seg = new GPXUtilities.TrkSegment(); @@ -2147,6 +2150,8 @@ public class GpxUiHelper { } } lastHeight = h; + } else { + lastHeight = HEIGHT_UNDEFINED; } seg.points.add(point); } diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteLineDrawInfo.java b/OsmAnd/src/net/osmand/plus/routing/RouteLineDrawInfo.java index 88a033edae..2bb2eec155 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteLineDrawInfo.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteLineDrawInfo.java @@ -6,12 +6,14 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import net.osmand.plus.track.GradientScaleType; import net.osmand.util.Algorithms; public class RouteLineDrawInfo { private static final String LINE_COLOR_DAY = "line_color_day"; private static final String LINE_COLOR_NIGHT = "line_color_night"; + private static final String LINE_COLOR_GRADIENT = "line_color_gradient"; private static final String LINE_WIDTH = "line_width"; private static final String NAVIGATION_ICON_ID = "navigation_icon_id"; private static final String NAVIGATION_ICON_COLOR = "navigation_icon_color"; @@ -24,6 +26,7 @@ public class RouteLineDrawInfo { @ColorInt private Integer colorDay; private Integer colorNight; + private GradientScaleType scaleType; private String width; // temporally parameters to show in preview @@ -37,9 +40,11 @@ public class RouteLineDrawInfo { public RouteLineDrawInfo(@Nullable @ColorInt Integer colorDay, @Nullable @ColorInt Integer colorNight, + @Nullable GradientScaleType gradientScaleType, @Nullable String width) { this.colorDay = colorDay; this.colorNight = colorNight; + this.scaleType = gradientScaleType; this.width = width; } @@ -50,6 +55,7 @@ public class RouteLineDrawInfo { public RouteLineDrawInfo(@NonNull RouteLineDrawInfo existed) { this.colorDay = existed.colorDay; this.colorNight = existed.colorNight; + this.scaleType = existed.scaleType; this.width = existed.width; this.iconId = existed.iconId; this.iconColor = existed.iconColor; @@ -71,6 +77,10 @@ public class RouteLineDrawInfo { this.useDefaultColor = useDefaultColor; } + public void setGradientScaleType(@Nullable GradientScaleType scaleType) { + this.scaleType = scaleType; + } + public void setWidth(@Nullable String width) { this.width = width; } @@ -108,6 +118,11 @@ public class RouteLineDrawInfo { return nightMode ? colorNight : colorDay; } + @Nullable + public GradientScaleType getGradientScaleType() { + return scaleType; + } + @Nullable public String getWidth() { return width; @@ -141,6 +156,9 @@ public class RouteLineDrawInfo { if (bundle.containsKey(LINE_COLOR_NIGHT)) { colorNight = bundle.getInt(LINE_COLOR_NIGHT); } + if (bundle.containsKey(LINE_COLOR_GRADIENT)) { + scaleType = GradientScaleType.getGradientTypeByName(bundle.getString(LINE_COLOR_GRADIENT)); + } width = bundle.getString(LINE_WIDTH); iconId = bundle.getInt(NAVIGATION_ICON_ID); iconColor = bundle.getInt(NAVIGATION_ICON_COLOR); @@ -157,6 +175,9 @@ public class RouteLineDrawInfo { if (colorNight != null) { bundle.putInt(LINE_COLOR_NIGHT, colorNight); } + if (scaleType != null) { + bundle.putString(LINE_COLOR_GRADIENT, scaleType.getTypeName()); + } if (width != null) { bundle.putString(LINE_WIDTH, width); } @@ -177,6 +198,7 @@ public class RouteLineDrawInfo { if (!Algorithms.objectEquals(getColor(false), that.getColor(false))) return false; if (!Algorithms.objectEquals(getColor(true), that.getColor(true))) return false; + if (!Algorithms.objectEquals(scaleType, that.scaleType)) return false; return Algorithms.objectEquals(width, that.width); } @@ -184,6 +206,7 @@ public class RouteLineDrawInfo { public int hashCode() { int result = colorDay != null ? colorDay.hashCode() : 0; result = 31 * result + (colorNight != null ? colorNight.hashCode() : 0); + result = 31 * result + (scaleType != null ? scaleType.getTypeName().hashCode() : 0); result = 31 * result + (width != null ? width.hashCode() : 0); return result; } diff --git a/OsmAnd/src/net/osmand/plus/routing/cards/RouteLineColorCard.java b/OsmAnd/src/net/osmand/plus/routing/cards/RouteLineColorCard.java index 643efd0ba7..586b7fd672 100644 --- a/OsmAnd/src/net/osmand/plus/routing/cards/RouteLineColorCard.java +++ b/OsmAnd/src/net/osmand/plus/routing/cards/RouteLineColorCard.java @@ -21,6 +21,7 @@ import net.osmand.AndroidUtils; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.helpers.ColorDialogs; import net.osmand.plus.helpers.enums.DayNightMode; import net.osmand.plus.routepreparationmenu.cards.BaseCard; @@ -32,6 +33,8 @@ import net.osmand.plus.settings.fragments.HeaderUiAdapter; import net.osmand.plus.track.AppearanceViewHolder; import net.osmand.plus.track.ColorsCard; import net.osmand.plus.track.CustomColorBottomSheet.ColorPickerListener; +import net.osmand.plus.track.GradientCard; +import net.osmand.plus.track.GradientScaleType; import net.osmand.plus.widgets.multistatetoggle.RadioItem; import net.osmand.plus.widgets.multistatetoggle.RadioItem.OnRadioItemClickListener; import net.osmand.plus.widgets.multistatetoggle.TextToggleButton; @@ -51,6 +54,7 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP private HeaderUiAdapter headerUiAdapter; private ColorsCard colorsCard; + private GradientCard gradientCard; private ColorTypeAdapter colorAdapter; private RecyclerView groupRecyclerView; private TextView tvDescription; @@ -64,7 +68,9 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP private enum ColorMode { DEFAULT(R.string.map_widget_renderer, R.drawable.ic_action_map_style), - CUSTOM(R.string.shared_string_custom, R.drawable.ic_action_settings); + CUSTOM(R.string.shared_string_custom, R.drawable.ic_action_settings), + ALTITUDE(R.string.altitude, R.drawable.ic_action_hillshade_dark), + SLOPE(R.string.shared_string_slope, R.drawable.ic_action_altitude_ascent); ColorMode(int titleId, int iconId) { this.titleId = titleId; @@ -108,28 +114,43 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP setupRadioGroup(radioGroup); cardsContainer = (ViewGroup) view.findViewById(R.id.colors_card_container); - createColorSelector(cardsContainer); + createCards(cardsContainer); initSelectedMode(); } private void initSelectedMode() { - selectedMode = getRouteLineColor() == null ? ColorMode.DEFAULT : ColorMode.CUSTOM; + if (routeLineDrawInfo.getGradientScaleType() == GradientScaleType.ALTITUDE) { + selectedMode = ColorMode.ALTITUDE; + } else if (routeLineDrawInfo.getGradientScaleType() == GradientScaleType.SLOPE) { + selectedMode = ColorMode.SLOPE; + } else { + selectedMode = getRouteLineColor() == null ? ColorMode.DEFAULT : ColorMode.CUSTOM; + } modeChanged(); } private void modeChanged() { if (selectedMode == ColorMode.DEFAULT) { themeToggleContainer.setVisibility(View.GONE); - cardsContainer.setVisibility(View.GONE); + AndroidUiHelper.updateVisibility(colorsCard.getView(), false); + AndroidUiHelper.updateVisibility(gradientCard.getView(), false); routeLineDrawInfo.setUseDefaultColor(true); changeMapTheme(initMapTheme); - } else { + } else if (selectedMode == ColorMode.CUSTOM) { themeToggleContainer.setVisibility(View.VISIBLE); - cardsContainer.setVisibility(View.VISIBLE); + AndroidUiHelper.updateVisibility(colorsCard.getView(), true); + AndroidUiHelper.updateVisibility(gradientCard.getView(), false); routeLineDrawInfo.setUseDefaultColor(false); changeMapTheme(isNightMap() ? DayNightMode.NIGHT : DayNightMode.DAY); + } else { + gradientCard.setSelectedScaleType(getGradientScaleTypeFromMode()); + AndroidUiHelper.updateVisibility(colorsCard.getView(), false); + AndroidUiHelper.updateVisibility(gradientCard.getView(), true); + themeToggleContainer.setVisibility(View.GONE); + routeLineDrawInfo.setUseDefaultColor(false); } + routeLineDrawInfo.setGradientScaleType(getGradientScaleTypeFromMode()); updateColorItems(); updateDescription(); } @@ -170,7 +191,7 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP } } - private void createColorSelector(ViewGroup container) { + private void createCards(ViewGroup container) { MapActivity mapActivity = getMapActivity(); if (mapActivity != null) { List colors = new ArrayList<>(); @@ -184,6 +205,9 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP colorsCard = new ColorsCard(mapActivity, selectedColor, targetFragment, colors, preference, null); colorsCard.setListener(this); container.addView(colorsCard.build(mapActivity)); + + gradientCard = new GradientCard(mapActivity, routeLineDrawInfo.getGradientScaleType()); + container.addView(gradientCard.build(mapActivity)); } } @@ -212,6 +236,17 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP return routeLineDrawInfo.getColor(isNightMap()); } + @Nullable + private GradientScaleType getGradientScaleTypeFromMode() { + if (selectedMode == ColorMode.ALTITUDE) { + return GradientScaleType.ALTITUDE; + } else if (selectedMode == ColorMode.SLOPE) { + return GradientScaleType.SLOPE; + } else { + return null; + } + } + private void updateSelectedColor() { int selectedColor = colorsCard.getSelectedColor(); routeLineDrawInfo.setColor(selectedColor, isNightMap()); @@ -235,6 +270,8 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP String colorName = ""; if (selectedMode == ColorMode.DEFAULT) { colorName = app.getString(R.string.map_widget_renderer); + } else if (selectedMode == ColorMode.ALTITUDE || selectedMode == ColorMode.SLOPE) { + colorName = app.getString(selectedMode.titleId); } else if (getRouteLineColor() != null) { int colorNameId = ColorDialogs.getColorName(getRouteLineColor()); colorName = app.getString(colorNameId); @@ -248,10 +285,12 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP String pattern = app.getString(R.string.route_line_use_map_style_appearance); String color = app.getString(R.string.shared_string_color).toLowerCase(); description = String.format(pattern, color, app.getRendererRegistry().getSelectedRendererName()); - } else { + } else if (selectedMode == ColorMode.CUSTOM) { String pattern = app.getString(R.string.specify_color_for_map_mode); String mapModeTitle = app.getString(isNightMap() ? NIGHT_TITLE_ID : DAY_TITLE_ID); description = String.format(pattern, mapModeTitle.toLowerCase()); + } else { + description = app.getString(R.string.route_line_use_gradient_coloring); } tvDescription.setText(description); } @@ -368,4 +407,4 @@ public class RouteLineColorCard extends BaseCard implements CardListener, ColorP void onMapThemeUpdated(@NonNull DayNightMode mapTheme); } -} +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java index b3f23444eb..2471e9f2c0 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java @@ -2708,6 +2708,7 @@ public class OsmandSettings { public final ListStringPreference CUSTOM_ROUTE_LINE_COLORS = (ListStringPreference) new ListStringPreference(this, "custom_route_line_colors", null, ",").makeShared().makeGlobal(); public final CommonPreference ROUTE_LINE_COLOR_DAY = new IntPreference(this, "route_line_color", 0).cache().makeProfile(); public final CommonPreference ROUTE_LINE_COLOR_NIGHT = new IntPreference(this, "route_line_color_night", 0).cache().makeProfile(); + public final CommonPreference ROUTE_LINE_GRADIENT = new EnumStringPreference<>(this, "route_line_gradient", null, new GradientScaleType[] {GradientScaleType.ALTITUDE, GradientScaleType.SLOPE}).cache().makeProfile(); public final CommonPreference ROUTE_LINE_WIDTH = new StringPreference(this, "route_line_width", null).makeProfile(); public final OsmandPreference USE_OSM_LIVE_FOR_ROUTING = new BooleanPreference(this, "enable_osmc_routing", true).makeProfile(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java index c8d600315c..9bae7b1088 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java @@ -63,6 +63,7 @@ import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.settings.fragments.RouteLineAppearanceFragment.OnApplyRouteLineListener; import net.osmand.plus.track.ColorsCard; import net.osmand.plus.track.CustomColorBottomSheet.ColorPickerListener; +import net.osmand.plus.track.GradientScaleType; import net.osmand.plus.widgets.FlowLayout; import net.osmand.plus.widgets.OsmandTextFieldBoxes; import net.osmand.util.Algorithms; @@ -1021,8 +1022,9 @@ public class ProfileAppearanceFragment extends BaseSettingsFragment implements O private RouteLineDrawInfo createRouteLineDrawInfo(@NonNull ApplicationMode appMode) { Integer colorDay = getRouteLineColor(appMode, settings.ROUTE_LINE_COLOR_DAY); Integer colorNight = getRouteLineColor(appMode, settings.ROUTE_LINE_COLOR_NIGHT); + GradientScaleType scaleType = settings.ROUTE_LINE_GRADIENT.getModeValue(appMode); String widthKey = settings.ROUTE_LINE_WIDTH.getModeValue(appMode); - return new RouteLineDrawInfo(colorDay, colorNight, widthKey); + return new RouteLineDrawInfo(colorDay, colorNight, scaleType, widthKey); } private Integer getRouteLineColor(@NonNull ApplicationMode appMode, @@ -1035,6 +1037,7 @@ public class ProfileAppearanceFragment extends BaseSettingsFragment implements O @NonNull RouteLineDrawInfo drawInfo) { saveRouteLineColor(appMode, settings.ROUTE_LINE_COLOR_DAY, drawInfo.getColor(false)); saveRouteLineColor(appMode, settings.ROUTE_LINE_COLOR_NIGHT, drawInfo.getColor(true)); + settings.ROUTE_LINE_GRADIENT.setModeValue(appMode, drawInfo.getGradientScaleType()); settings.ROUTE_LINE_WIDTH.setModeValue(appMode, drawInfo.getWidth()); } diff --git a/OsmAnd/src/net/osmand/plus/track/GradientCard.java b/OsmAnd/src/net/osmand/plus/track/GradientCard.java index 3c34d9f68d..de2623decf 100644 --- a/OsmAnd/src/net/osmand/plus/track/GradientCard.java +++ b/OsmAnd/src/net/osmand/plus/track/GradientCard.java @@ -19,15 +19,24 @@ import androidx.annotation.Nullable; public class GradientCard extends BaseCard { - private GPXTrackAnalysis gpxTrackAnalysis; + private final GPXTrackAnalysis gpxTrackAnalysis; private GradientScaleType selectedScaleType; + private final int minSlope = 0; + private final int maxSlope = 60; + public GradientCard(@NonNull MapActivity mapActivity, @NonNull GPXTrackAnalysis gpxTrackAnalysis, @Nullable GradientScaleType selectedScaleType) { super(mapActivity); this.gpxTrackAnalysis = gpxTrackAnalysis; this.selectedScaleType = selectedScaleType; } + public GradientCard(@NonNull MapActivity mapActivity, @Nullable GradientScaleType scaleType) { + super(mapActivity); + this.gpxTrackAnalysis = null; + this.selectedScaleType = scaleType; + } + @Override public int getCardLayoutId() { return R.layout.gradient_card; @@ -40,14 +49,27 @@ public class GradientCard extends BaseCard { return; } - AndroidUiHelper.updateVisibility(view, true); TextView minValue = view.findViewById(R.id.min_value); TextView maxValue = view.findViewById(R.id.max_value); - double min = RouteColorize.getMinValue(selectedScaleType.toColorizationType(), gpxTrackAnalysis); - double max = RouteColorize.getMaxValue(selectedScaleType.toColorizationType(), - gpxTrackAnalysis, min, app.getSettings().getApplicationMode().getMaxSpeed()); - minValue.setText(formatValue(min)); - maxValue.setText(formatValue(max)); + + if (gpxTrackAnalysis != null) { + AndroidUiHelper.updateVisibility(view, true); + double min = RouteColorize.getMinValue(selectedScaleType.toColorizationType(), gpxTrackAnalysis); + double max = RouteColorize.getMaxValue(selectedScaleType.toColorizationType(), + gpxTrackAnalysis, min, app.getSettings().getApplicationMode().getMaxSpeed()); + minValue.setText(formatValue(min)); + maxValue.setText(formatValue(max)); + AndroidUiHelper.updateVisibility(view.findViewById(R.id.space), true); + } else { + if (selectedScaleType == GradientScaleType.ALTITUDE) { + minValue.setText(R.string.shared_string_min_height); + maxValue.setText(R.string.shared_string_max_height); + } else if (selectedScaleType == GradientScaleType.SLOPE) { + minValue.setText(formatValue(minSlope)); + maxValue.setText(formatValue(maxSlope)); + } + AndroidUiHelper.updateVisibility(view.findViewById(R.id.space), false); + } } public void setSelectedScaleType(GradientScaleType type) { @@ -59,7 +81,7 @@ public class GradientCard extends BaseCard { if (selectedScaleType == GradientScaleType.ALTITUDE) { return OsmAndFormatter.getFormattedAlt(value, app); } else if (selectedScaleType == GradientScaleType.SLOPE) { - return (int) value + " %"; + return app.getString(R.string.ltr_or_rtl_combine_via_space, String.valueOf((int) value), "%"); } String speed = OsmAndFormatter.getFormattedSpeed((float) value, app); String speedUnit = app.getSettings().SPEED_SYSTEM.get().toShortString(app); diff --git a/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java index 01e6765000..9ebf2eb093 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java @@ -4,6 +4,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Cap; @@ -11,6 +12,7 @@ import android.graphics.Path; import android.graphics.PointF; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; +import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; @@ -44,6 +46,7 @@ import net.osmand.plus.routing.RouteService; import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.routing.TransportRoutingHelper; import net.osmand.plus.settings.backend.CommonPreference; +import net.osmand.plus.track.GradientScaleType; import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider; @@ -54,6 +57,7 @@ import net.osmand.plus.views.layers.geometry.RouteGeometryWayContext; import net.osmand.render.RenderingRuleProperty; import net.osmand.render.RenderingRuleSearchRequest; import net.osmand.render.RenderingRulesStorage; +import net.osmand.router.RouteColorize; import net.osmand.router.TransportRouteResult; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; @@ -109,6 +113,7 @@ public class RouteLayer extends OsmandMapLayer implements IContextMenuProvider { private int routeLineColor; private Integer directionArrowsColor; + private GradientScaleType gradientScaleType = null; public RouteLayer(RoutingHelper helper) { this.helper = helper; @@ -136,6 +141,8 @@ public class RouteLayer extends OsmandMapLayer implements IContextMenuProvider { attrs.defaultWidth = (int) (12 * density); attrs.defaultWidth3 = (int) (7 * density); attrs.defaultColor = view.getResources().getColor(R.color.nav_track); + attrs.shadowPaint.setColor(0x80000000); + attrs.shadowPaint.setStrokeCap(Cap.ROUND); attrs.paint3.setStrokeCap(Cap.BUTT); attrs.paint3.setColor(Color.WHITE); attrs.paint2.setStrokeCap(Cap.BUTT); @@ -331,14 +338,24 @@ public class RouteLayer extends OsmandMapLayer implements IContextMenuProvider { DrawSettings settings, RouteLineDrawInfo drawInfo) { updateAttrs(settings, tileBox); - updateRouteColors(nightMode); - paintRouteLinePreview.setColor(getRouteLineColor()); paintRouteLinePreview.setStrokeWidth(getRouteLineWidth(tileBox)); int centerX = drawInfo.getCenterX(); int centerY = drawInfo.getCenterY(); int screenHeight = drawInfo.getScreenHeight(); + updateRouteColors(nightMode); + updateRouteGradient(); + + LinearGradient gradient = null; + if (gradientScaleType == GradientScaleType.ALTITUDE || gradientScaleType == GradientScaleType.SLOPE) { + int[] colors = new int[] {RouteColorize.RED, RouteColorize.YELLOW, RouteColorize.GREEN}; + float[] positions = new float[] {0, 0.5f, 1}; + gradient = new LinearGradient(centerX, 0, centerX, screenHeight, colors, positions, Shader.TileMode.CLAMP); + } + paintRouteLinePreview.setShader(gradient); + paintRouteLinePreview.setColor(getRouteLineColor()); + canvas.drawLine(centerX, 0, centerX, screenHeight, paintRouteLinePreview); if (previewIcon == null) { @@ -460,6 +477,14 @@ public class RouteLayer extends OsmandMapLayer implements IContextMenuProvider { routeLineColor = color; } + private void updateRouteGradient() { + if (routeLineDrawInfo != null) { + gradientScaleType = routeLineDrawInfo.getGradientScaleType(); + } else { + gradientScaleType = view.getSettings().ROUTE_LINE_GRADIENT.getModeValue(helper.getAppMode()); + } + } + private float getRouteLineWidth(@NonNull RotatedTileBox tileBox) { String widthKey; if (routeLineDrawInfo != null) { @@ -524,9 +549,10 @@ public class RouteLayer extends OsmandMapLayer implements IContextMenuProvider { boolean directTo = route.getRouteService() == RouteService.DIRECT_TO; boolean straight = route.getRouteService() == RouteService.STRAIGHT; publicTransportRouteGeometry.clearRoute(); - routeGeometry.updateRoute(tb, route); updateRouteColors(nightMode); - routeGeometry.setRouteStyleParams(getRouteLineColor(), getRouteLineWidth(tb), getDirectionArrowsColor()); + updateRouteGradient(); + routeGeometry.setRouteStyleParams(getRouteLineColor(), getRouteLineWidth(tb), getDirectionArrowsColor(), gradientScaleType); + routeGeometry.updateRoute(tb, route, view.getApplication()); if (directTo) { routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, null, 0); diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java index aed7a2f315..9bb4bf9445 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java @@ -126,7 +126,7 @@ public abstract class GeometryWay zooms) { int zoom = tb.getZoom(); PathGeometryZoom zm = zooms.size() > zoom ? zooms.get(zoom) : null; if (zm == null) { @@ -139,6 +139,11 @@ public abstract class GeometryWay getDefaultWayStyle(); + @NonNull + protected Map> getStyleMap() { + return styleMap; + } + public Location getNextVisiblePoint() { return null; } @@ -148,7 +153,7 @@ public abstract class GeometryWay odistances = geometryZoom.getDistances(); @@ -168,32 +173,27 @@ public abstract class GeometryWay> styleMap, int locationIdx) { + return simplification.getQuick(locationIdx) == 0 && !styleMap.containsKey(locationIdx); + } + protected boolean shouldAddLocation(TByteArrayList simplification, double leftLon, double rightLon, double bottomLat, double topLat, GeometryWayProvider provider, int currLocationIdx) { double lat = provider.getLatitude(currLocationIdx); @@ -214,7 +218,14 @@ public abstract class GeometryWay style, + protected void addLocation(RotatedTileBox tb, int locationIdx, GeometryWayStyle style, + List tx, List ty, List angles, List distances, + double dist, List> styles) { + addLocation(tb, locationProvider.getLatitude(locationIdx), locationProvider.getLongitude(locationIdx), + style, tx, ty, angles, distances, dist, styles); + } + + protected void addLocation(RotatedTileBox tb, double latitude, double longitude, GeometryWayStyle style, List tx, List ty, List angles, List distances, double dist, List> styles) { float x = tb.getPixXFromLatLon(latitude, longitude); @@ -356,13 +367,26 @@ public abstract class GeometryWay>> paths = new ArrayList<>(); canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY()); calculatePath(tb, tx, ty, styles, paths); - for (Pair> pc : paths) { + + drawer.drawFullBorder(canvas, tb.getZoom(), paths); + drawer.drawSegmentBorder(canvas, tb.getZoom(), paths.get(0).first, paths.get(0).second); + for (int i = 1; i <= paths.size(); i++) { + if (i != paths.size()) { + Pair> prev = paths.get(i); + if (prev.second.hasPathLine()) { + drawer.drawSegmentBorder(canvas, tb.getZoom(), prev.first, prev.second); + } + } + + Pair> pc = paths.get(i - 1); GeometryWayStyle style = pc.second; if (style.hasPathLine()) { drawer.drawPath(canvas, pc.first, style); } } context.clearCustomColor(); + context.clearCustomShader(); + } drawer.drawArrowsOverPath(canvas, tb, tx, ty, angles, distances, distToFinish, styles); } finally { @@ -372,7 +396,7 @@ public abstract class GeometryWay(size); if (simplify) { simplifyPoints.fill(0, size, (byte) 0); - if (size > 0) { - simplifyPoints.set(0, (byte) 1); - } - double distInPix = (tb.getDistance(0, 0, tb.getPixWidth(), 0) / tb.getPixWidth()); - double cullDistance = (distInPix * (EPSILON_IN_DPI * Math.max(1, tb.getDensity()))); - cullRamerDouglasPeucker(simplifyPoints, locationProvider, 0, size - 1, cullDistance); + simplify(tb, locationProvider, simplifyPoints); } else { simplifyPoints.fill(0, size, (byte) 1); } @@ -422,6 +441,16 @@ public abstract class GeometryWay 0) { + simplifyPoints.set(0, (byte) 1); + } + double distInPix = (tb.getDistance(0, 0, tb.getPixWidth(), 0) / tb.getPixWidth()); + double cullDistance = (distInPix * (EPSILON_IN_DPI * Math.max(1, tb.getDensity()))); + cullRamerDouglasPeucker(simplifyPoints, locationProvider, 0, size - 1, cullDistance); + } + public List getDistances() { return distances; } diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java index d53160bd04..c4a58478d6 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java @@ -105,6 +105,10 @@ public abstract class GeometryWayContext { attrs.customColor = 0; } + public void clearCustomShader() { + attrs.customColorPaint.setShader(null); + } + public int getStrokeColor(int sourceColor) { return ColorUtils.blendARGB(sourceColor, Color.BLACK, 0.6f); } diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java index 494375d404..2b607563e5 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java @@ -7,6 +7,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.util.Pair; import net.osmand.data.RotatedTileBox; @@ -89,6 +90,12 @@ public class GeometryWayDrawer { } } + protected void drawFullBorder(Canvas canvas, int zoom, List>> paths) { + } + + protected void drawSegmentBorder(Canvas canvas, int zoom, Path path, GeometryWayStyle style) { + } + protected PathPoint getArrowPathPoint(float iconx, float icony, GeometryWayStyle style, double angle) { return new PathPoint(iconx, icony, angle, style); } diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/RouteGeometryWay.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/RouteGeometryWay.java index d46ca8cd68..a21a6ad29a 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/RouteGeometryWay.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/RouteGeometryWay.java @@ -2,20 +2,32 @@ package net.osmand.plus.views.layers.geometry; import android.graphics.Bitmap; import android.graphics.Paint; +import android.graphics.PointF; + +import net.osmand.GPXUtilities; +import net.osmand.GPXUtilities.GPXFile; +import net.osmand.Location; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.helpers.GpxUiHelper; +import net.osmand.plus.routing.RouteCalculationResult; +import net.osmand.plus.routing.RoutingHelper; +import net.osmand.plus.track.GradientScaleType; +import net.osmand.router.RouteColorize; +import net.osmand.router.RouteColorize.RouteColorizationPoint; +import net.osmand.util.Algorithms; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import gnu.trove.list.array.TByteArrayList; -import net.osmand.Location; -import net.osmand.data.RotatedTileBox; -import net.osmand.plus.routing.RouteCalculationResult; -import net.osmand.plus.routing.RoutingHelper; - -import java.util.Collections; -import java.util.List; - -public class RouteGeometryWay extends GeometryWay> { +public class RouteGeometryWay extends GeometryWay { private RoutingHelper helper; private RouteCalculationResult route; @@ -23,34 +35,101 @@ public class RouteGeometryWay extends GeometryWay(context)); + super(context, new RouteGeometryWayDrawer(context, true)); this.helper = context.getApp().getRoutingHelper(); } public void setRouteStyleParams(@Nullable @ColorInt Integer color, @Nullable Float width, - @Nullable @ColorInt Integer pointColor) { + @Nullable @ColorInt Integer pointColor, + @Nullable GradientScaleType scaleType) { + if (scaleType != null && !Algorithms.objectEquals(customWidth, width)) { + for (GeometryWayStyle style : getStyleMap().values()) { + style.width = width; + } + } this.customColor = color; this.customWidth = width; this.customPointColor = pointColor; + this.prevScaleType = this.scaleType; + this.scaleType = scaleType; + if (width != null) { + getContext().getAttrs().shadowPaint.setStrokeWidth(width + getContext().getDensity() * 2); + } + getContext().getAttrs().customColorPaint.setStrokeCap(scaleType != null ? Paint.Cap.ROUND : Paint.Cap.BUTT); } - @NonNull - @Override - public GeometryWayStyle getDefaultWayStyle() { - Paint paint = getContext().getAttrs().paint; - int color = customColor != null ? customColor : paint.getColor(); - float width = customWidth != null ? customWidth : paint.getStrokeWidth(); - return new GeometrySolidWayStyle(getContext(), color, width, customPointColor); - } - - public void updateRoute(RotatedTileBox tb, RouteCalculationResult route) { - if (tb.getMapDensity() != getMapDensity() || this.route != route) { + public void updateRoute(RotatedTileBox tb, RouteCalculationResult route, OsmandApplication app) { + if (tb.getMapDensity() != getMapDensity() || this.route != route || prevScaleType != scaleType) { this.route = route; List locations = route != null ? route.getImmutableAllLocations() : Collections.emptyList(); - updateWay(locations, tb); + + if (scaleType == null || locations.size() < 2) { + updateWay(locations, tb); + return; + } + GPXFile gpxFile = GpxUiHelper.makeGpxFromLocations(locations, app); + if (!gpxFile.hasAltitude) { + updateWay(locations, tb); + return; + } + + // Start point can have wrong zero altitude + List pts = gpxFile.tracks.get(0).segments.get(0).points; + GPXUtilities.WptPt firstPt = pts.get(0); + if (firstPt.ele == 0) { + firstPt.ele = pts.get(1).ele; + } + + RouteColorize routeColorize = new RouteColorize(tb.getZoom(), gpxFile, null, scaleType.toColorizationType(), 0); + List points = routeColorize.getResult(false); + + updateWay(new GradientGeometryWayProvider(routeColorize, points), createStyles(points), tb); + } + } + + private Map> createStyles(List points) { + Map> styleMap = new TreeMap<>(); + for (int i = 1; i < points.size(); i++) { + GeometryGradientWayStyle style = getGradientWayStyle(); + style.startColor = points.get(i - 1).color; + style.endColor = points.get(i).color; + styleMap.put(i, style); + } + return styleMap; + } + + @Override + protected boolean shouldSkipLocation(TByteArrayList simplification, Map> styleMap, int locationIdx) { + return scaleType == null ? + super.shouldSkipLocation(simplification, styleMap, locationIdx) : + simplification.getQuick(locationIdx) == 0; + } + + @Override + protected void addLocation(RotatedTileBox tb, int locationIdx, GeometryWayStyle style, List tx, List ty, List angles, List distances, double dist, List> styles) { + super.addLocation(tb, getLocationProvider().getLatitude(locationIdx), + getLocationProvider().getLongitude(locationIdx), style, tx, ty, angles, distances, dist, styles); + if (scaleType != null && tx.size() - 1 > 0) { + int idx = tx.size() - 1; + ((GeometryGradientWayStyle) style).startXY = new PointF(tx.get(idx - 1), ty.get(idx - 1)); + ((GeometryGradientWayStyle) style).endXY = new PointF(tx.get(idx), ty.get(idx)); + } + } + + @Override + protected void addLocation(RotatedTileBox tb, double latitude, double longitude, GeometryWayStyle style, List tx, List ty, List angles, List distances, double dist, List> styles) { + super.addLocation(tb, latitude, longitude, style, tx, ty, angles, distances, dist, styles); + if (scaleType != null) { + int lastIdx = tx.size() - 1; + ((GeometryGradientWayStyle) style).startXY = new PointF(tx.get(lastIdx), ty.get(lastIdx)); + ((GeometryGradientWayStyle) style).endXY = new PointF(tx.get(lastIdx), ty.get(lastIdx)); + ((GeometryGradientWayStyle) style).startColor = getGradientLocationProvider().getColor(0); + ((GeometryGradientWayStyle) style).endColor = getGradientLocationProvider().getColor(0); } } @@ -61,17 +140,125 @@ public class RouteGeometryWay extends GeometryWay getDefaultWayStyle() { + Paint paint = getContext().getAttrs().paint; + int color = customColor != null ? customColor : paint.getColor(); + float width = customWidth != null ? customWidth : paint.getStrokeWidth(); + return scaleType == null ? + new GeometrySolidWayStyle(getContext(), color, width, customPointColor) : + new GeometryGradientWayStyle(getContext(), color, width); + } + + private GeometryGradientWayStyle getGradientWayStyle() { + return (GeometryGradientWayStyle) getDefaultWayStyle(); + } + @Override public Location getNextVisiblePoint() { return helper.getRoute().getCurrentStraightAnglePoint(); } + private GradientGeometryWayProvider getGradientLocationProvider() { + return (GradientGeometryWayProvider) getLocationProvider(); + } + + @Override + protected PathGeometryZoom getGeometryZoom(RotatedTileBox tb, Map zooms) { + if (scaleType == null) { + return super.getGeometryZoom(tb, zooms); + } + int zoom = tb.getZoom(); + PathGeometryZoom zm = zooms.get(zoom); + if (zm == null) { + zm = new GradientPathGeometryZoom(getLocationProvider(), tb, true); + zooms.put(zoom, zm); + } + return zm; + } + + private static class GradientGeometryWayProvider implements GeometryWayProvider { + + private final RouteColorize routeColorize; + private final List locations; + + public GradientGeometryWayProvider(RouteColorize routeColorize, List locations) { + this.routeColorize = routeColorize; + this.locations = locations; + } + + public List simplify(int zoom) { + return routeColorize.simplify(zoom); + } + + public int getColor(int index) { + return locations.get(index).color; + } + + @Override + public double getLatitude(int index) { + return locations.get(index).lat; + } + + @Override + public double getLongitude(int index) { + return locations.get(index).lon; + } + + @Override + public int getSize() { + return locations.size(); + } + } + + private static class GradientPathGeometryZoom extends PathGeometryZoom { + + public GradientPathGeometryZoom(GeometryWayProvider locationProvider, RotatedTileBox tb, boolean simplify) { + super(locationProvider, tb, simplify); + } + + @Override + protected void simplify(RotatedTileBox tb, GeometryWayProvider locationProvider, TByteArrayList simplifyPoints) { + if (locationProvider instanceof GradientGeometryWayProvider) { + GradientGeometryWayProvider provider = (GradientGeometryWayProvider) locationProvider; + List simplified = provider.simplify(tb.getZoom()); + for (RouteColorizationPoint location : simplified) { + simplifyPoints.set(location.id, (byte) 1); + } + } + } + } + + public static class GeometryGradientWayStyle extends GeometryWayStyle { + + public int startColor; + public int endColor; + + public PointF startXY; + public PointF endXY; + + public GeometryGradientWayStyle(RouteGeometryWayContext context, Integer color, Float width) { + super(context, color, width); + } + + @Override + public Bitmap getPointBitmap() { + return getContext().getArrowBitmap(); + } + + @Override + public boolean equals(Object other) { + return this == other; + } + } + private static class GeometrySolidWayStyle extends GeometryWayStyle { private Integer pointColor; GeometrySolidWayStyle(RouteGeometryWayContext context, Integer color, Float width, - Integer pointColor) { + Integer pointColor) { super(context, color, width); this.pointColor = pointColor; } @@ -97,4 +284,4 @@ public class RouteGeometryWay extends GeometryWay { + + private static final int BORDER_TYPE_ZOOM_THRESHOLD = MapTileLayer.DEFAULT_MAX_ZOOM + MapTileLayer.OVERZOOM_IN; + + private final boolean drawBorder; + + public RouteGeometryWayDrawer(RouteGeometryWayContext context, boolean drawBorder) { + super(context); + this.drawBorder = drawBorder; + } + + @Override + protected void drawFullBorder(Canvas canvas, int zoom, List>> paths) { + if (drawBorder && zoom < BORDER_TYPE_ZOOM_THRESHOLD) { + Paint borderPaint = getContext().getAttrs().shadowPaint; + Path fullPath = new Path(); + for (Pair> path : paths) { + if (path.second instanceof GeometryGradientWayStyle) { + fullPath.addPath(path.first); + } + } + canvas.drawPath(fullPath, borderPaint); + } + } + + @Override + public void drawPath(Canvas canvas, Path path, GeometryWayStyle s) { + if (s instanceof GeometryGradientWayStyle) { + GeometryGradientWayStyle style = (GeometryGradientWayStyle) s; + LinearGradient gradient = new LinearGradient(style.startXY.x, style.startXY.y, style.endXY.x, style.endXY.y, + style.startColor, style.endColor, Shader.TileMode.CLAMP); + getContext().getAttrs().customColorPaint.setShader(gradient); + } + super.drawPath(canvas, path, s); + } + + @Override + protected void drawSegmentBorder(Canvas canvas, int zoom, Path path, GeometryWayStyle style) { + if (drawBorder && zoom >= BORDER_TYPE_ZOOM_THRESHOLD) { + canvas.drawPath(path, getContext().getAttrs().shadowPaint); + } + } +} \ No newline at end of file