From b0b5528cc6430628ae78e9cd0ca15d6bdcd0de70 Mon Sep 17 00:00:00 2001 From: cepprice Date: Wed, 10 Mar 2021 16:55:55 +0500 Subject: [PATCH] Add gradient coloring of track --- .../main/java/net/osmand/GPXUtilities.java | 17 ++++ .../java/net/osmand/router/RouteColorize.java | 18 +++- .../net/osmand/plus/track/TrackDrawInfo.java | 16 ++++ .../src/net/osmand/plus/views/Renderable.java | 52 ++++++++++- .../osmand/plus/views/layers/GPXLayer.java | 87 ++++++++++++++++++- 5 files changed, 180 insertions(+), 10 deletions(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java index cb5f40a4b9..ae4080b367 100644 --- a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java +++ b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java @@ -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); } 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 63f0ee1cd3..301a69a101 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java @@ -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,9 +25,9 @@ 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 GREEN = rgbaToDecimal(90, 220, 95, 1); - public static final int YELLOW = rgbaToDecimal(212, 239, 50, 1); - public static final int RED = rgbaToDecimal(243, 55, 77, 1); + 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 ColorizationType { @@ -202,6 +203,17 @@ public class RouteColorize { sortPalette(); } + public void setPalette(int[] gradientPalette) { + if (gradientPalette.length != 3) { + return; + } + setPalette(new double[][] { + {minValue, gradientPalette[0]}, + {colorizationType == ColorizationType.SLOPE ? 12.5 : (minValue + maxValue) / 2}, + {maxValue, gradientPalette[0]} + }); + } + private int getDefaultColor() { return rgbaToDecimal(0, 0, 0, 0); } diff --git a/OsmAnd/src/net/osmand/plus/track/TrackDrawInfo.java b/OsmAnd/src/net/osmand/plus/track/TrackDrawInfo.java index 18d1c30775..f08f87546d 100644 --- a/OsmAnd/src/net/osmand/plus/track/TrackDrawInfo.java +++ b/OsmAnd/src/net/osmand/plus/track/TrackDrawInfo.java @@ -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; @@ -46,6 +49,9 @@ public class TrackDrawInfo { width = gpxDataItem.getWidth(); gradientScaleType = gpxDataItem.getGradientScaleType(); color = gpxDataItem.getColor(); + speedGradientPalette = gpxDataItem.getGradientSpeedPalette(); + altitudeGradientPalette = gpxDataItem.getGradientAltitudePalette(); + slopeGradientPalette = gpxDataItem.getGradientSlopePalette(); splitType = gpxDataItem.getSplitType(); splitInterval = gpxDataItem.getSplitInterval(); joinSegments = gpxDataItem.isJoinSegments(); @@ -82,6 +88,16 @@ 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 getSplitType() { return splitType; } diff --git a/OsmAnd/src/net/osmand/plus/views/Renderable.java b/OsmAnd/src/net/osmand/plus/views/Renderable.java index ec8d302dab..e8d4a6183f 100644 --- a/OsmAnd/src/net/osmand/plus/views/Renderable.java +++ b/OsmAnd/src/net/osmand/plus/views/Renderable.java @@ -1,8 +1,10 @@ package net.osmand.plus.views; import android.graphics.Canvas; +import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.Shader; import androidx.annotation.NonNull; @@ -10,7 +12,7 @@ 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; @@ -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; } @@ -124,7 +131,7 @@ public class Renderable { } } - protected void draw(List pts, Paint p, Canvas canvas, RotatedTileBox tileBox) { + protected void drawSolid(List pts, Paint p, Canvas canvas, RotatedTileBox tileBox) { if (pts.size() > 1) { updateLocalPaint(p); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); @@ -160,6 +167,38 @@ public class Renderable { canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); } } + + protected void drawGradient(List pts, Paint p, Canvas canvas, RotatedTileBox tileBox) { + if (pts.size() < 2) { + return; + } + + updateLocalPaint(p); + canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); + QuadRect tileBounds = tileBox.getLatLonBounds(); + WptPt prevPt = pts.get(0); + for (int i = 1; i < pts.size(); i++) { + WptPt currentPt = pts.get(i); + if (Math.min(currentPt.lon, prevPt.lon) < tileBounds.right && Math.max(currentPt.lon, prevPt.lon) > tileBounds.left + && Math.min(currentPt.lat, prevPt.lat) < tileBounds.top && Math.max(currentPt.lat, prevPt.lat) > tileBounds.bottom) { + 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 paint = new Paint(this.paint); + paint.setShader(gradient); + Path path = new Path(); + path.moveTo(startX, startY); + path.lineTo(endX, endY); + canvas.drawPath(path, paint); + } + prevPt = currentPt; + } + canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); + } } public static class StandardTrack extends RenderableSegment { @@ -193,7 +232,12 @@ public class Renderable { } @Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { - draw(culled.isEmpty() ? points : culled, p, canvas, tileBox); + if (scaleType != null) { + drawGradient(getPointsForDrawing(), p, canvas, tileBox); + scaleType = null; + } else { + drawSolid(getPointsForDrawing(), p, canvas, tileBox); + } } } @@ -215,7 +259,7 @@ 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); + drawSolid(points, p, canvas, tileBox); } } } diff --git a/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java index 339192e6b6..abb99e8428 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java @@ -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,7 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM private CommonPreference defaultTrackWidthPref; private CommonPreference currentTrackColorPref; + private CommonPreference currentTrackScaleType; private CommonPreference currentTrackWidthPref; private CommonPreference currentTrackShowArrowsPref; private CommonPreference currentTrackShowStartFinishPref; @@ -155,6 +158,7 @@ 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; currentTrackWidthPref = view.getSettings().CURRENT_TRACK_WIDTH; currentTrackShowArrowsPref = view.getSettings().CURRENT_TRACK_SHOW_ARROWS; currentTrackShowStartFinishPref = view.getSettings().CURRENT_TRACK_SHOW_START_FINISH; @@ -661,10 +665,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 segments = selectedGpxFile.getPointsToDisplay(); + GradientScaleType scaleType = getGradientScaleType(gpxFile); + List 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 +693,45 @@ 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; + if (scaleType != null) { + renderableSegment.setGradientScaleType(scaleType); + } + renderableSegment.drawSegment(view.getZoom(), paint, canvas, tileBox); } } } + private int setColorsToPoints(TrkSegment segment, List 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 +752,37 @@ 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); + } + 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)) {