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..62186a8bb3 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])); @@ -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++) { diff --git a/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java index 01e6765000..f2f1ba24b5 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; @@ -331,14 +336,22 @@ 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(); + 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}; + LinearGradient gradient = new LinearGradient(centerX, 0, centerX, screenHeight, colors, positions, Shader.TileMode.CLAMP); + paintRouteLinePreview.setShader(gradient); + } else { + updateRouteColors(nightMode); + paintRouteLinePreview.setColor(getRouteLineColor()); + } + canvas.drawLine(centerX, 0, centerX, screenHeight, paintRouteLinePreview); if (previewIcon == null) { @@ -524,9 +537,9 @@ 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); + routeGeometry.setRouteStyleParams(getRouteLineColor(), getRouteLineWidth(tb), getDirectionArrowsColor(), gradientScaleType); + routeGeometry.updateRoute(tb, route, view.getApplication()); updateRouteColors(nightMode); - routeGeometry.setRouteStyleParams(getRouteLineColor(), getRouteLineWidth(tb), getDirectionArrowsColor()); 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..7e9a511286 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) { @@ -148,7 +148,7 @@ public abstract class GeometryWay odistances = geometryZoom.getDistances(); @@ -171,29 +171,24 @@ 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); @@ -372,7 +374,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 +419,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/RouteGeometryWay.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/RouteGeometryWay.java index d46ca8cd68..bbac19c8fe 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,30 @@ package net.osmand.plus.views.layers.geometry; import android.graphics.Bitmap; import android.graphics.Paint; +import android.graphics.PointF; + +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 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 +33,82 @@ public class RouteGeometryWay extends GeometryWay(context)); + super(context, new RouteGeometryWayDrawer(context)); 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) { this.customColor = color; this.customWidth = width; this.customPointColor = pointColor; + this.scaleType = GradientScaleType.ALTITUDE; } - @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, OsmandApplication app) { + if (tb.getMapDensity() == getMapDensity() && this.route == route) { + return; + } - public void updateRoute(RotatedTileBox tb, RouteCalculationResult route) { - if (tb.getMapDensity() != getMapDensity() || this.route != route) { - this.route = route; - List locations = route != null ? route.getImmutableAllLocations() : Collections.emptyList(); + this.route = route; + List locations = route != null ? route.getImmutableAllLocations() : Collections.emptyList(); + + if (scaleType == null || locations.size() < 2) { updateWay(locations, tb); + return; + } + + GPXFile gpxFile = GpxUiHelper.makeGpxFromRoute(route, app); + if (!gpxFile.hasAltitude) { + updateWay(locations, tb); + } + + RouteColorize routeColorize = new RouteColorize(tb.getZoom(), gpxFile, null, scaleType.toColorizationType(), 0); + List points = routeColorize.getResult(false); + + updateWay(new GradientGeometryWayProvider(routeColorize), 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 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).startColor = getGradientLocationProvider().getColor(0); + ((GeometryGradientWayStyle) style).endColor = getGradientLocationProvider().getColor(0); + if (lastIdx != 0) { + ((GeometryGradientWayStyle) styles.get(lastIdx - 1)).endXY = new PointF(tx.get(lastIdx - 1), ty.get(lastIdx - 1)); + } } } @@ -61,17 +119,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(), 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) { + this.routeColorize = routeColorize; + locations = routeColorize.getResult(false); + } + + 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, Float width) { + super(context, 0xFFFFFFFF, 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 +263,4 @@ public class RouteGeometryWay extends GeometryWay { + + + public RouteGeometryWayDrawer(RouteGeometryWayContext context) { + super(context); + } + + @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); + getContext().getAttrs().customColorPaint.setStrokeCap(Paint.Cap.ROUND); + } + super.drawPath(canvas, path, s); + } +} \ No newline at end of file