Add gradient coloring of track

This commit is contained in:
cepprice 2021-03-10 16:55:55 +05:00
parent a6cedd2767
commit b0b5528cc6
5 changed files with 180 additions and 10 deletions

View file

@ -6,6 +6,7 @@ import net.osmand.binary.StringBundle;
import net.osmand.binary.StringBundleWriter; import net.osmand.binary.StringBundleWriter;
import net.osmand.binary.StringBundleXmlWriter; import net.osmand.binary.StringBundleXmlWriter;
import net.osmand.data.QuadRect; import net.osmand.data.QuadRect;
import net.osmand.router.RouteColorize.ColorizationType;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -227,6 +228,9 @@ public class GPXUtilities {
public double hdop = Double.NaN; public double hdop = Double.NaN;
public float heading = Float.NaN; public float heading = Float.NaN;
public boolean deleted = false; 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 int colourARGB = 0; // point colour (used for altitude/speed colouring)
public double distance = 0.0; // cumulative distance, if in a track public double distance = 0.0; // cumulative distance, if in a track
@ -249,6 +253,9 @@ public class GPXUtilities {
this.hdop = wptPt.hdop; this.hdop = wptPt.hdop;
this.heading = wptPt.heading; this.heading = wptPt.heading;
this.deleted = wptPt.deleted; this.deleted = wptPt.deleted;
this.speedColor = wptPt.speedColor;
this.altitudeColor = wptPt.altitudeColor;
this.slopeColor = wptPt.slopeColor;
this.colourARGB = wptPt.colourARGB; this.colourARGB = wptPt.colourARGB;
this.distance = wptPt.distance; this.distance = wptPt.distance;
} }
@ -311,6 +318,16 @@ public class GPXUtilities {
getExtensionsToWrite().put(ICON_NAME_EXTENSION, iconName); 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() { public String getBackgroundType() {
return getExtensionsToRead().get(BACKGROUND_TYPE_EXTENSION); return getExtensionsToRead().get(BACKGROUND_TYPE_EXTENSION);
} }

View file

@ -5,6 +5,7 @@ import net.osmand.PlatformUtil;
import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.OsmMapUtils; import net.osmand.osm.edit.OsmMapUtils;
import net.osmand.util.MapUtils; import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import java.util.ArrayList; 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 DARK_GREY = rgbaToDecimal(92, 92, 92, 255);
public static final int LIGHT_GREY = rgbaToDecimal(200, 200, 200, 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 GREEN = rgbaToDecimal(90, 220, 95, 255);
public static final int YELLOW = rgbaToDecimal(212, 239, 50, 1); public static final int YELLOW = rgbaToDecimal(212, 239, 50, 255);
public static final int RED = rgbaToDecimal(243, 55, 77, 1); public static final int RED = rgbaToDecimal(243, 55, 77, 255);
public static final int[] colors = new int[] {GREEN, YELLOW, RED}; public static final int[] colors = new int[] {GREEN, YELLOW, RED};
public enum ColorizationType { public enum ColorizationType {
@ -202,6 +203,17 @@ public class RouteColorize {
sortPalette(); 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() { private int getDefaultColor() {
return rgbaToDecimal(0, 0, 0, 0); return rgbaToDecimal(0, 0, 0, 0);
} }

View file

@ -26,6 +26,9 @@ public class TrackDrawInfo {
private String width; private String width;
private GradientScaleType gradientScaleType; private GradientScaleType gradientScaleType;
private int color; private int color;
private int[] speedGradientPalette;
private int[] altitudeGradientPalette;
private int[] slopeGradientPalette;
private int splitType; private int splitType;
private double splitInterval; private double splitInterval;
private boolean joinSegments; private boolean joinSegments;
@ -46,6 +49,9 @@ public class TrackDrawInfo {
width = gpxDataItem.getWidth(); width = gpxDataItem.getWidth();
gradientScaleType = gpxDataItem.getGradientScaleType(); gradientScaleType = gpxDataItem.getGradientScaleType();
color = gpxDataItem.getColor(); color = gpxDataItem.getColor();
speedGradientPalette = gpxDataItem.getGradientSpeedPalette();
altitudeGradientPalette = gpxDataItem.getGradientAltitudePalette();
slopeGradientPalette = gpxDataItem.getGradientSlopePalette();
splitType = gpxDataItem.getSplitType(); splitType = gpxDataItem.getSplitType();
splitInterval = gpxDataItem.getSplitInterval(); splitInterval = gpxDataItem.getSplitInterval();
joinSegments = gpxDataItem.isJoinSegments(); joinSegments = gpxDataItem.isJoinSegments();
@ -82,6 +88,16 @@ public class TrackDrawInfo {
this.color = color; 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() { public int getSplitType() {
return splitType; return splitType;
} }

View file

@ -1,8 +1,10 @@
package net.osmand.plus.views; package net.osmand.plus.views;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Shader;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -10,7 +12,7 @@ import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.WptPt; import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.QuadRect; import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox; 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.plus.views.layers.geometry.GpxGeometryWay;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -65,6 +67,7 @@ public class Renderable {
protected double zoom = -1; protected double zoom = -1;
protected AsynchronousResampler culler = null; // The currently active resampler protected AsynchronousResampler culler = null; // The currently active resampler
protected Paint paint = null; // MUST be set by 'updateLocalPaint' before use protected Paint paint = null; // MUST be set by 'updateLocalPaint' before use
protected GradientScaleType scaleType = null;
protected GpxGeometryWay geometryWay; protected GpxGeometryWay geometryWay;
@ -85,6 +88,10 @@ public class Renderable {
paint.setStrokeWidth(p.getStrokeWidth()); paint.setStrokeWidth(p.getStrokeWidth());
} }
public void setGradientScaleType(GradientScaleType type) {
this.scaleType = type;
}
public GpxGeometryWay getGeometryWay() { public GpxGeometryWay getGeometryWay() {
return geometryWay; return geometryWay;
} }
@ -124,7 +131,7 @@ public class Renderable {
} }
} }
protected void draw(List<WptPt> pts, Paint p, Canvas canvas, RotatedTileBox tileBox) { protected void drawSolid(List<WptPt> pts, Paint p, Canvas canvas, RotatedTileBox tileBox) {
if (pts.size() > 1) { if (pts.size() > 1) {
updateLocalPaint(p); updateLocalPaint(p);
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
@ -160,6 +167,38 @@ public class Renderable {
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
} }
} }
protected void drawGradient(List<WptPt> 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 { 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) { @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 protected void startCuller(double newZoom) {}
@Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { @Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {
draw(points, p, canvas, tileBox); drawSolid(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.render.OsmandRenderer.RenderingContext;
import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu; import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu;
import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.track.GradientScaleType;
import net.osmand.plus.track.SaveGpxAsyncTask; import net.osmand.plus.track.SaveGpxAsyncTask;
import net.osmand.plus.track.TrackDrawInfo; import net.osmand.plus.track.TrackDrawInfo;
import net.osmand.plus.views.OsmandMapLayer; 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.RenderingRuleProperty;
import net.osmand.render.RenderingRuleSearchRequest; import net.osmand.render.RenderingRuleSearchRequest;
import net.osmand.render.RenderingRulesStorage; import net.osmand.render.RenderingRulesStorage;
import net.osmand.router.RouteColorize;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils; import net.osmand.util.MapUtils;
@ -142,6 +144,7 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
private CommonPreference<String> defaultTrackWidthPref; private CommonPreference<String> defaultTrackWidthPref;
private CommonPreference<Integer> currentTrackColorPref; private CommonPreference<Integer> currentTrackColorPref;
private CommonPreference<GradientScaleType> currentTrackScaleType;
private CommonPreference<String> currentTrackWidthPref; private CommonPreference<String> currentTrackWidthPref;
private CommonPreference<Boolean> currentTrackShowArrowsPref; private CommonPreference<Boolean> currentTrackShowArrowsPref;
private CommonPreference<Boolean> currentTrackShowStartFinishPref; private CommonPreference<Boolean> currentTrackShowStartFinishPref;
@ -155,6 +158,7 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
osmandRenderer = view.getApplication().getResourceManager().getRenderer().getRenderer(); osmandRenderer = view.getApplication().getResourceManager().getRenderer().getRenderer();
currentTrackColorPref = view.getSettings().CURRENT_TRACK_COLOR; currentTrackColorPref = view.getSettings().CURRENT_TRACK_COLOR;
currentTrackScaleType = view.getSettings().CURRENT_TRACK_COLORIZATION;
currentTrackWidthPref = view.getSettings().CURRENT_TRACK_WIDTH; currentTrackWidthPref = view.getSettings().CURRENT_TRACK_WIDTH;
currentTrackShowArrowsPref = view.getSettings().CURRENT_TRACK_SHOW_ARROWS; currentTrackShowArrowsPref = view.getSettings().CURRENT_TRACK_SHOW_ARROWS;
currentTrackShowStartFinishPref = view.getSettings().CURRENT_TRACK_SHOW_START_FINISH; 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, private void drawSelectedFileSegments(SelectedGpxFile selectedGpxFile, boolean currentTrack, Canvas canvas,
RotatedTileBox tileBox, DrawSettings settings) { RotatedTileBox tileBox, DrawSettings settings) {
GPXFile gpxFile = selectedGpxFile.getGpxFile();
List<TrkSegment> segments = selectedGpxFile.getPointsToDisplay(); 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) { for (TrkSegment ts : segments) {
String width = getTrackWidthName(selectedGpxFile.getGpxFile(), defaultTrackWidthPref.get()); String width = getTrackWidthName(gpxFile, defaultTrackWidthPref.get());
int color = getTrackColor(selectedGpxFile.getGpxFile(), ts.getColor(cachedColor)); int color = getTrackColor(gpxFile, ts.getColor(cachedColor));
if (colorsOfPoints != null) {
startIdx = setColorsToPoints(ts, colorsOfPoints, scaleType, startIdx);
}
if (ts.renderer == null && !ts.points.isEmpty()) { if (ts.renderer == null && !ts.points.isEmpty()) {
Renderable.RenderableSegment renderer; Renderable.RenderableSegment renderer;
if (currentTrack) { if (currentTrack) {
@ -677,11 +693,45 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
} }
updatePaints(color, width, selectedGpxFile.isRoutePoints(), currentTrack, settings, tileBox); updatePaints(color, width, selectedGpxFile.isRoutePoints(), currentTrack, settings, tileBox);
if (ts.renderer instanceof Renderable.RenderableSegment) { 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<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) { private float getTrackWidth(String width, float defaultTrackWidth) {
Float trackWidth = cachedTrackWidth.get(width); Float trackWidth = cachedTrackWidth.get(width);
return trackWidth != null ? trackWidth : defaultTrackWidth; return trackWidth != null ? trackWidth : defaultTrackWidth;
@ -702,6 +752,37 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
return color != 0 ? color : defaultColor; 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) { private String getTrackWidthName(GPXFile gpxFile, String defaultWidth) {
String width = null; String width = null;
if (hasTrackDrawInfoForTrack(gpxFile)) { if (hasTrackDrawInfoForTrack(gpxFile)) {