From 3da32a65cfc59b98f68d7db1b32166716ab1a0e7 Mon Sep 17 00:00:00 2001 From: cepprice Date: Wed, 7 Apr 2021 23:49:13 +0500 Subject: [PATCH 1/6] Proper implementation of route colorization algorithm --- .../main/java/net/osmand/GPXUtilities.java | 19 ++++ .../java/net/osmand/router/RouteColorize.java | 2 +- .../src/net/osmand/plus/views/Renderable.java | 25 ++---- .../osmand/plus/views/layers/GPXLayer.java | 86 ++++++++----------- 4 files changed, 63 insertions(+), 69 deletions(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java index ae4080b367..06ee6689be 100644 --- a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java +++ b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java @@ -328,6 +328,16 @@ public class GPXUtilities { } } + public void setColor(ColorizationType type, int color) { + if (type == ColorizationType.SPEED) { + speedColor = color; + } else if (type == ColorizationType.ELEVATION) { + altitudeColor = color; + } else if (type == ColorizationType.SLOPE) { + slopeColor = color; + } + } + public String getBackgroundType() { return getExtensionsToRead().get(BACKGROUND_TYPE_EXTENSION); } @@ -1103,6 +1113,15 @@ public class GPXUtilities { return trackBounds; } + public static QuadRect calculateTrackBounds(List segments) { + QuadRect trackBounds = new QuadRect(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); + for (TrkSegment segment : segments) { + updateBounds(trackBounds, segment.points, 0); + } + return trackBounds; + } + public static void updateBounds(QuadRect trackBounds, List pts, int startIndex) { for (int i = startIndex; i < pts.size(); i++) { WptPt pt = pts.get(i); 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 0a39d57e25..88b6555e19 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java @@ -477,7 +477,7 @@ public class RouteColorize { } public static class RouteColorizationPoint { - int id; + public int id; public double lat; public double lon; public double val; diff --git a/OsmAnd/src/net/osmand/plus/views/Renderable.java b/OsmAnd/src/net/osmand/plus/views/Renderable.java index 657cf3856a..e02a9d13f8 100644 --- a/OsmAnd/src/net/osmand/plus/views/Renderable.java +++ b/OsmAnd/src/net/osmand/plus/views/Renderable.java @@ -28,7 +28,6 @@ import java.util.concurrent.atomic.AtomicInteger; import androidx.annotation.NonNull; - public class Renderable { private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); @@ -61,7 +60,6 @@ public class Renderable { public List points = null; // Original list of points protected List culled = new ArrayList<>(); // Reduced/resampled list of points - protected List oldCulled = new ArrayList<>(); protected int pointSize; protected double segmentSize; @@ -89,6 +87,9 @@ public class Renderable { } paint.setColor(p.getColor()); paint.setStrokeWidth(p.getStrokeWidth()); + if (scaleType != null) { + paint.setAlpha(0xFF); + } } public void setBorderPaint(@NonNull Paint paint) { @@ -117,20 +118,18 @@ public class Renderable { updateLocalPaint(p); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); if (scaleType != null) { - drawGradient(getPointsForDrawingWithBorder(), p, canvas, tileBox); + drawGradient(points, p, canvas, tileBox); } else { drawSolid(getPointsForDrawing(), p, canvas, tileBox); } canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); } - public void drawSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { if (QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) { // is visible? - if (tileBox.getZoomAnimation() > 0 && !Algorithms.isEmpty(culled) && scaleType != null) { - oldCulled = new ArrayList<>(culled); + if (scaleType == null) { + startCuller(zoom); } - startCuller(zoom); drawSingleSegment(zoom, p, canvas, tileBox); } } @@ -143,16 +142,6 @@ public class Renderable { return culled.isEmpty() ? points : culled; } - public List getPointsForDrawingWithBorder() { - if (!culled.isEmpty()) { - return culled; - } else if (!oldCulled.isEmpty()) { - return oldCulled; - } else { - return points; - } - } - public void drawGeometry(Canvas canvas, RotatedTileBox tileBox, QuadRect quadRect, int arrowColor, int trackColor, float trackWidth) { if (geometryWay != null) { List points = getPointsForDrawing(); @@ -316,4 +305,4 @@ public class Renderable { @Override protected void startCuller(double newZoom) {} } -} +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java index 69266746a9..7948e7fd8d 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java @@ -68,6 +68,8 @@ import net.osmand.render.RenderingRuleProperty; import net.osmand.render.RenderingRuleSearchRequest; import net.osmand.render.RenderingRulesStorage; import net.osmand.router.RouteColorize; +import net.osmand.router.RouteColorize.ColorizationType; +import net.osmand.router.RouteColorize.RouteColorizationPoint; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; @@ -680,26 +682,29 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM private void drawSelectedFileSegments(SelectedGpxFile selectedGpxFile, boolean currentTrack, Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + boolean visible = QuadRect.trivialOverlap(tileBox.getLatLonBounds(), GPXUtilities.calculateTrackBounds(selectedGpxFile.getModifiablePointsToDisplay())); + if (!selectedGpxFile.getGpxFile().hasTrkPt() || !visible) { + return; + } + OsmandApplication app = view.getApplication(); GPXFile gpxFile = selectedGpxFile.getGpxFile(); - List segments = selectedGpxFile.getPointsToDisplay(); GradientScaleType scaleType = getGradientScaleType(gpxFile); - List colorsOfPoints = null; + List segments = new ArrayList<>(); - if (needCalculatePointsColors(segments, scaleType)) { + if (scaleType == null) { + segments.addAll(selectedGpxFile.getModifiablePointsToDisplay()); + } else { RouteColorize colorize = new RouteColorize(view.getZoom(), gpxFile, selectedGpxFile.getTrackAnalysis(app), scaleType.toColorizationType(), app.getSettings().getApplicationMode().getMaxSpeed()); colorize.setPalette(getColorizationPalette(gpxFile, scaleType)); - colorsOfPoints = colorize.getResult(false); + List colorsOfPoints = colorize.getResult(true); + segments.addAll(createSimplifiedSegmentsFromColorizedPoints(gpxFile, colorsOfPoints, scaleType)); } - int startIdx = 0; for (TrkSegment ts : segments) { 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) { @@ -720,53 +725,34 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM } } - private boolean needCalculatePointsColors(List segments, GradientScaleType scaleType) { - if (scaleType == null) { - return false; - } - RouteColorize.ColorizationType colorizationType = scaleType.toColorizationType(); - for (int segIdx = segments.size() - 1; segIdx >= 0; segIdx--) { - List pts = segments.get(segIdx).points; - if (!Algorithms.isEmpty(pts)) { - for (int wptIdx = pts.size() - 1; wptIdx >= 0; wptIdx--) { - WptPt pt = pts.get(wptIdx); - if (pt.getColor(colorizationType) == 0) { - return true; + private List createSimplifiedSegmentsFromColorizedPoints(GPXFile gpxFile, + List colorizationPoints, + GradientScaleType scaleType) { + List simplifiedSegments = new ArrayList<>(); + ColorizationType colorizationType = scaleType.toColorizationType(); + int id = 0; + int colorPointIdx = 0; + + for (GPXUtilities.Track track : gpxFile.tracks) { + for (TrkSegment segment : track.segments) { + TrkSegment simplifiedSegment = new TrkSegment(); + simplifiedSegments.add(simplifiedSegment); + for (WptPt pt : segment.points) { + if (colorPointIdx >= colorizationPoints.size()) { + return simplifiedSegments; } + RouteColorizationPoint colorPoint = colorizationPoints.get(colorPointIdx); + if (colorPoint.id == id) { + simplifiedSegment.points.add(pt); + pt.setColor(colorizationType, colorPoint.color); + colorPointIdx++; + } + id++; } } } - return false; - } - 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; + return simplifiedSegments; } private float getTrackWidth(String width, float defaultTrackWidth) { From 74a534c4f33c4e9609c010c4e12c39d8eaaf400d Mon Sep 17 00:00:00 2001 From: cepprice Date: Sun, 11 Apr 2021 18:46:33 +0500 Subject: [PATCH 2/6] Fix inconsistency of track geometry --- .../java/net/osmand/osm/edit/OsmMapUtils.java | 24 +++++++++++++++++++ .../java/net/osmand/router/RouteColorize.java | 10 ++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java b/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java index cf86ca88ed..c4823cf9ea 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java @@ -619,6 +619,30 @@ public class OsmMapUtils { return new Cell(x / area, y / area, 0, rings); } + public static void simplifyDouglasPeucker(List nodes, int start, int end, List survivedNodes, double epsilon) { + double dmax = Double.NEGATIVE_INFINITY; + int index = -1; + + Node startPt = nodes.get(start); + Node endPt = nodes.get(end); + + for (int i = start + 1; i < end; i++) { + Node pt = nodes.get(i); + double d = MapUtils.getOrthogonalDistance(pt.getLatitude(), pt.getLongitude(), + startPt.getLatitude(), startPt.getLongitude(), endPt.getLatitude(), endPt.getLongitude()); + if (d > dmax) { + dmax = d; + index = i; + } + } + if (dmax > epsilon) { + simplifyDouglasPeucker(nodes, start, index, survivedNodes, epsilon); + simplifyDouglasPeucker(nodes, index, end, survivedNodes, epsilon); + } else { + survivedNodes.add(nodes.get(end)); + } + } + private static class CellComparator implements Comparator { @Override public int compare(Cell o1, Cell o2) { 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 88b6555e19..6abb9c47c0 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteColorize.java @@ -35,6 +35,7 @@ public class RouteColorize { public static final int RED = rgbaToDecimal(243, 55, 77, 255); public static final int[] colors = new int[] {GREEN, YELLOW, RED}; + private static final float DEFAULT_BASE = 17.2f; private static final int MAX_SLOPE_VALUE = 25; public enum ColorizationType { @@ -241,7 +242,6 @@ public class RouteColorize { if (dataList == null) { dataList = new ArrayList<>(); for (int i = 0; i < latitudes.length; i++) { - //System.out.println(latitudes[i] + " " + longitudes[i] + " " + values[i]); dataList.add(new RouteColorizationPoint(i, latitudes[i], longitudes[i], values[i])); } } @@ -250,11 +250,13 @@ public class RouteColorize { for (RouteColorizationPoint data : dataList) { nodes.add(new net.osmand.osm.edit.Node(data.lat, data.lon, data.id)); } - OsmMapUtils.simplifyDouglasPeucker(nodes, zoom + 5, 1, result, true); + + double epsilon = Math.pow(2.0, DEFAULT_BASE - zoom); + result.add(nodes.get(0)); + OsmMapUtils.simplifyDouglasPeucker(nodes, 0, nodes.size() - 1, result, epsilon); List simplified = new ArrayList<>(); - - for (int i = 1; i < result.size() - 1; i++) { + for (int i = 1; i < result.size(); i++) { int prevId = (int) result.get(i - 1).getId(); int currentId = (int) result.get(i).getId(); List sublist = dataList.subList(prevId, currentId); From 430ff544b3d2e743890e6299517763cbcefec99a Mon Sep 17 00:00:00 2001 From: cepprice Date: Sun, 11 Apr 2021 23:34:01 +0500 Subject: [PATCH 3/6] Cache simplified colored tracks --- .../osmand/plus/views/layers/GPXLayer.java | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java index 7948e7fd8d..8b3494822e 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java @@ -118,6 +118,9 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM private MapMarkersHelper mapMarkersHelper; private GpxSelectionHelper selectedGpxHelper; + private final Map cachedModifiedTime = new HashMap<>(); + private final Map> cachedZoomedSegments = new HashMap<>(); + private List cache = new ArrayList<>(); private Map pointFileMap = new HashMap<>(); private MapTextLayer textLayer; @@ -460,7 +463,10 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM float trackWidth = getTrackWidth(width, defaultTrackWidth); int trackColor = getTrackColor(selectedGpxFile.getGpxFile(), cachedColor); int arrowColor = UiUtilities.getContrastColor(view.getApplication(), trackColor, false); - for (TrkSegment segment : selectedGpxFile.getPointsToDisplay()) { + GradientScaleType scaleType = getGradientScaleType(selectedGpxFile.getGpxFile()); + List segments = scaleType != null ? + getCachedSegments(selectedGpxFile, scaleType) : selectedGpxFile.getPointsToDisplay(); + for (TrkSegment segment : segments) { if (segment.renderer instanceof Renderable.RenderableSegment) { ((Renderable.RenderableSegment) segment.renderer) .drawGeometry(canvas, tileBox, correctedQuadRect, arrowColor, trackColor, trackWidth); @@ -687,7 +693,6 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM return; } - OsmandApplication app = view.getApplication(); GPXFile gpxFile = selectedGpxFile.getGpxFile(); GradientScaleType scaleType = getGradientScaleType(gpxFile); List segments = new ArrayList<>(); @@ -695,11 +700,7 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM if (scaleType == null) { segments.addAll(selectedGpxFile.getModifiablePointsToDisplay()); } else { - RouteColorize colorize = new RouteColorize(view.getZoom(), gpxFile, selectedGpxFile.getTrackAnalysis(app), - scaleType.toColorizationType(), app.getSettings().getApplicationMode().getMaxSpeed()); - colorize.setPalette(getColorizationPalette(gpxFile, scaleType)); - List colorsOfPoints = colorize.getResult(true); - segments.addAll(createSimplifiedSegmentsFromColorizedPoints(gpxFile, colorsOfPoints, scaleType)); + segments.addAll(getCachedSegments(selectedGpxFile, scaleType)); } for (TrkSegment ts : segments) { @@ -725,6 +726,43 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM } } + private List getCachedSegments(SelectedGpxFile selectedGpxFile, GradientScaleType scaleType) { + GPXFile gpxFile = selectedGpxFile.getGpxFile(); + String fileName = gpxFile.path; + String trackId = view.getZoom() + "_" + scaleType.toString() + "_" + fileName; + + if (cachedModifiedTime.get(fileName) != null && cachedModifiedTime.get(fileName) == gpxFile.modifiedTime) { + List segments = cachedZoomedSegments.get(trackId); + if (segments == null) { + segments = calculateGradientTrack(selectedGpxFile, scaleType); + cachedZoomedSegments.put(trackId, segments); + } + return segments; + } else { + if (cachedModifiedTime.get(fileName) != null) { + for (String key : cachedZoomedSegments.keySet()) { + if (key.contains("_" + fileName)) { + cachedZoomedSegments.remove(key); + } + } + } + cachedModifiedTime.put(fileName, gpxFile.modifiedTime); + List segments = calculateGradientTrack(selectedGpxFile, scaleType); + cachedZoomedSegments.put(trackId, segments); + return segments; + } + } + + private List calculateGradientTrack(SelectedGpxFile selectedGpxFile, GradientScaleType scaleType) { + OsmandApplication app = view.getApplication(); + GPXFile gpxFile = selectedGpxFile.getGpxFile(); + RouteColorize colorize = new RouteColorize(view.getZoom(), gpxFile, selectedGpxFile.getTrackAnalysis(app), + scaleType.toColorizationType(), app.getSettings().getApplicationMode().getMaxSpeed()); + colorize.setPalette(getColorizationPalette(gpxFile, scaleType)); + List colorsOfPoints = colorize.getResult(true); + return createSimplifiedSegmentsFromColorizedPoints(selectedGpxFile.getGpxFile(), colorsOfPoints, scaleType); + } + private List createSimplifiedSegmentsFromColorizedPoints(GPXFile gpxFile, List colorizationPoints, GradientScaleType scaleType) { From 5d8f6c81d13ad23fa4cf9c707f45508fdd33a19e Mon Sep 17 00:00:00 2001 From: cepprice Date: Mon, 12 Apr 2021 16:22:28 +0500 Subject: [PATCH 4/6] Fix overlapping of border projection and minimize overlapping of gradient projection --- .../main/java/net/osmand/util/MapUtils.java | 4 +- .../src/net/osmand/plus/views/Renderable.java | 144 +++++++++++------- 2 files changed, 90 insertions(+), 58 deletions(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java b/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java index 97e921a3d4..7704a2685d 100644 --- a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java +++ b/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java @@ -747,7 +747,9 @@ public class MapUtils { return Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)); } - + public static double getSqrtDistance(float startX, float startY, float endX, float endY) { + return Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)); + } } diff --git a/OsmAnd/src/net/osmand/plus/views/Renderable.java b/OsmAnd/src/net/osmand/plus/views/Renderable.java index e02a9d13f8..dd8ba55b8a 100644 --- a/OsmAnd/src/net/osmand/plus/views/Renderable.java +++ b/OsmAnd/src/net/osmand/plus/views/Renderable.java @@ -14,6 +14,7 @@ import net.osmand.data.RotatedTileBox; import net.osmand.plus.track.GradientScaleType; import net.osmand.plus.views.layers.geometry.GpxGeometryWay; import net.osmand.util.Algorithms; +import net.osmand.util.MapUtils; import java.util.ArrayList; import java.util.List; @@ -118,9 +119,10 @@ public class Renderable { updateLocalPaint(p); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); if (scaleType != null) { - drawGradient(points, p, canvas, tileBox); + drawSolid(points, borderPaint, canvas, tileBox); + drawGradient(points, paint, canvas, tileBox); } else { - drawSolid(getPointsForDrawing(), p, canvas, tileBox); + drawSolid(getPointsForDrawing(), paint, canvas, tileBox); } canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); } @@ -166,7 +168,7 @@ public class Renderable { float lastX = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon); float lastY = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon); if (!path.isEmpty()) { - canvas.drawPath(path, paint); + canvas.drawPath(path, p); } path.reset(); path.moveTo(lastX, lastY); @@ -180,58 +182,99 @@ public class Renderable { lastPt = pt; } if (!path.isEmpty()) { - canvas.drawPath(path, paint); + canvas.drawPath(path, p); } } protected void drawGradient(List pts, Paint p, Canvas canvas, RotatedTileBox tileBox) { QuadRect tileBounds = tileBox.getLatLonBounds(); - Path currentPath = new Path(); - Path nextPath = new Path(); - Paint paint = new Paint(this.paint); + Path path = new Path(); + boolean recalculateLastXY = true; + WptPt lastPt = pts.get(0); - WptPt prevWpt = pts.get(0); - WptPt currWpt = pts.get(1); - - PointF prevXY = new PointF(); - PointF currXY = new PointF(); - PointF nextXY = new PointF(); - - boolean currLineVisible = arePointsInsideTile(prevWpt, currWpt, tileBounds); - boolean nextLineVisible; - - if (currLineVisible) { - pixXYFromWptPt(tileBox, prevXY, prevWpt); - pixXYFromWptPt(tileBox, currXY, currWpt); - canvas.drawPath(pathFromStartEnd(currentPath, prevXY, currXY), borderPaint); - } + List gradientPoints = new ArrayList<>(); + List gradientColors = new ArrayList<>(); + float gradientAngle = 0; for (int i = 1; i < pts.size(); i++) { - currWpt = pts.get(i); - WptPt nextWpt = i + 1 == pts.size() ? null : pts.get(i + 1); + WptPt pt = pts.get(i); + WptPt nextPt = i + 1 < pts.size() ? pts.get(i + 1) : null; + float nextX = nextPt == null ? 0 : tileBox.getPixXFromLatLon(nextPt.lat, nextPt.lon); + float nextY = nextPt == null ? 0 : tileBox.getPixYFromLatLon(nextPt.lat, nextPt.lon); + float lastX = 0; + float lastY = 0; + if (arePointsInsideTile(pt, lastPt, tileBounds)) { + if (recalculateLastXY) { + recalculateLastXY = false; + lastX = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon); + lastY = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon); + if (!path.isEmpty()) { + p.setShader(createGradient(gradientPoints, gradientColors)); + canvas.drawPath(path, p); + } + path.reset(); + path.moveTo(lastX, lastY); - nextLineVisible = arePointsInsideTile(currWpt, nextWpt, tileBounds); - if (nextWpt != null && nextLineVisible) { - pixXYFromWptPt(tileBox, currXY, currWpt); - pixXYFromWptPt(tileBox, nextXY, nextWpt); - canvas.drawPath(pathFromStartEnd(nextPath, currXY, nextXY), borderPaint); + gradientPoints.clear(); + gradientColors.clear(); + gradientPoints.add(new PointF(lastX, lastY)); + gradientColors.add(lastPt.getColor(scaleType.toColorizationType())); + } + float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon); + float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon); + path.lineTo(x, y); + gradientPoints.add(new PointF(x, y)); + gradientColors.add(pt.getColor(scaleType.toColorizationType())); + + if (gradientColors.size() == 2) { + gradientAngle = calculateAngle(lastX, lastY, x, y); + } + if (nextPt != null) { + float nextAngle = calculateAngle(x, y, nextX, nextY); + if (Math.abs(nextAngle - gradientAngle) > 20) { + recalculateLastXY = true; + } + } + } else { + recalculateLastXY = true; } - - if (currLineVisible) { - int prevColor = prevWpt.getColor(scaleType.toColorizationType()); - int currentColor = currWpt.getColor(scaleType.toColorizationType()); - LinearGradient gradient = new LinearGradient(prevXY.x, prevXY.y, currXY.x, currXY.y, - prevColor, currentColor, Shader.TileMode.CLAMP); - paint.setShader(gradient); - canvas.drawPath(currentPath, paint); - } - - prevWpt = currWpt; - currentPath.set(nextPath); - prevXY.set(currXY); - currXY.set(nextXY); - currLineVisible = nextLineVisible; + lastPt = pt; } + if (!path.isEmpty()) { + p.setShader(createGradient(gradientPoints, gradientColors)); + canvas.drawPath(path, p); + } + } + + private LinearGradient createGradient(List gradientPoints, List gradientColors) { + float gradientLength = 0; + List pointsLength = new ArrayList<>(gradientPoints.size() - 1); + for (int i = 1; i < gradientPoints.size(); i++) { + PointF start = gradientPoints.get(i - 1); + PointF end = gradientPoints.get(i); + pointsLength.add((float) MapUtils.getSqrtDistance(start.x, start.y, end.x, end.y)); + gradientLength += pointsLength.get(i - 1); + } + + float[] positions = new float[gradientPoints.size()]; + positions[0] = 0; + for (int i = 1; i < gradientPoints.size(); i++) { + positions[i] = positions[i - 1] + pointsLength.get(i - 1) / gradientLength; + } + + int[] colors = new int[gradientColors.size()]; + for (int i = 0; i < gradientColors.size(); i++) { + colors[i] = gradientColors.get(i); + } + + PointF gradientStart = gradientPoints.get(0); + PointF gradientEnd = gradientPoints.get(gradientPoints.size() - 1); + return new LinearGradient(gradientStart.x, gradientStart.y, gradientEnd.x, gradientEnd.y, + colors, positions, Shader.TileMode.CLAMP); + } + + private float calculateAngle(float startX, float startY, float endX, float endY) { + return (float) Math.abs(Math.toDegrees(Math.atan2(endY - startY, endX - startX))); } protected boolean arePointsInsideTile(WptPt first, WptPt second, QuadRect tileBounds) { @@ -241,19 +284,6 @@ public class Renderable { return Math.min(first.lon, second.lon) < tileBounds.right && Math.max(first.lon, second.lon) > tileBounds.left && Math.min(first.lat, second.lat) < tileBounds.top && Math.max(first.lat, second.lat) > tileBounds.bottom; } - - protected PointF pixXYFromWptPt(RotatedTileBox tileBox, PointF pointF, WptPt wptPt) { - pointF.x = tileBox.getPixXFromLatLon(wptPt.lat, wptPt.lon); - pointF.y = tileBox.getPixYFromLatLon(wptPt.lat, wptPt.lon); - return pointF; - } - - protected Path pathFromStartEnd(Path path, PointF start, PointF end) { - path.reset(); - path.moveTo(start.x, start.y); - path.lineTo(end.x, end.y); - return path; - } } public static class StandardTrack extends RenderableSegment { From 9713d04c7f5a11ba27dd6acf66c8be6c366cbbbb Mon Sep 17 00:00:00 2001 From: cepprice Date: Mon, 12 Apr 2021 16:52:32 +0500 Subject: [PATCH 5/6] Fix not renderable slope track --- .../src/net/osmand/plus/track/TrackColoringCard.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java b/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java index 9e48ca0bbe..63b69b7e1c 100644 --- a/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java +++ b/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java @@ -11,6 +11,7 @@ import android.widget.TextView; import com.google.android.material.snackbar.Snackbar; import net.osmand.AndroidUtils; +import net.osmand.GPXUtilities.Elevation; import net.osmand.GPXUtilities.GPXTrackAnalysis; import net.osmand.PlatformUtil; import net.osmand.plus.R; @@ -92,7 +93,15 @@ public class TrackColoringCard extends BaseCard { if (scaleType == GradientScaleType.SPEED) { return gpxTrackAnalysis.isSpeedSpecified(); } else { - return gpxTrackAnalysis.isElevationSpecified(); + if (!gpxTrackAnalysis.isElevationSpecified()) { + return false; + } + for (Elevation elevation : gpxTrackAnalysis.elevationData) { + if (Float.isNaN(elevation.elevation)) { + return false; + } + } + return true; } } From 367b2bc27e9b24e68ac952ca02cae11f0b3a512d Mon Sep 17 00:00:00 2001 From: cepprice Date: Tue, 13 Apr 2021 10:40:31 +0500 Subject: [PATCH 6/6] Review fixes --- .../java/net/osmand/osm/edit/OsmMapUtils.java | 48 +++--- .../osmand/plus/views/layers/GPXLayer.java | 148 ++++++++++-------- 2 files changed, 106 insertions(+), 90 deletions(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java b/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java index c4823cf9ea..a29763ee82 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java @@ -340,6 +340,30 @@ public class OsmMapUtils { } } + public static void simplifyDouglasPeucker(List nodes, int start, int end, List survivedNodes, double epsilon) { + double dmax = Double.NEGATIVE_INFINITY; + int index = -1; + + Node startPt = nodes.get(start); + Node endPt = nodes.get(end); + + for (int i = start + 1; i < end; i++) { + Node pt = nodes.get(i); + double d = MapUtils.getOrthogonalDistance(pt.getLatitude(), pt.getLongitude(), + startPt.getLatitude(), startPt.getLongitude(), endPt.getLatitude(), endPt.getLongitude()); + if (d > dmax) { + dmax = d; + index = i; + } + } + if (dmax > epsilon) { + simplifyDouglasPeucker(nodes, start, index, survivedNodes, epsilon); + simplifyDouglasPeucker(nodes, index, end, survivedNodes, epsilon); + } else { + survivedNodes.add(nodes.get(end)); + } + } + private static double orthogonalDistance(int zoom, Node nodeLineStart, Node nodeLineEnd, Node node) { LatLon p = MapUtils.getProjection(node.getLatitude(), node.getLongitude(), nodeLineStart.getLatitude(), nodeLineStart.getLongitude(), nodeLineEnd.getLatitude(), nodeLineEnd.getLongitude()); @@ -619,30 +643,6 @@ public class OsmMapUtils { return new Cell(x / area, y / area, 0, rings); } - public static void simplifyDouglasPeucker(List nodes, int start, int end, List survivedNodes, double epsilon) { - double dmax = Double.NEGATIVE_INFINITY; - int index = -1; - - Node startPt = nodes.get(start); - Node endPt = nodes.get(end); - - for (int i = start + 1; i < end; i++) { - Node pt = nodes.get(i); - double d = MapUtils.getOrthogonalDistance(pt.getLatitude(), pt.getLongitude(), - startPt.getLatitude(), startPt.getLongitude(), endPt.getLatitude(), endPt.getLongitude()); - if (d > dmax) { - dmax = d; - index = i; - } - } - if (dmax > epsilon) { - simplifyDouglasPeucker(nodes, start, index, survivedNodes, epsilon); - simplifyDouglasPeucker(nodes, index, end, survivedNodes, epsilon); - } else { - survivedNodes.add(nodes.get(end)); - } - } - private static class CellComparator implements Comparator { @Override public int compare(Cell o1, Cell o2) { diff --git a/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java index 8b3494822e..d06d4077c6 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/GPXLayer.java @@ -118,8 +118,7 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM private MapMarkersHelper mapMarkersHelper; private GpxSelectionHelper selectedGpxHelper; - private final Map cachedModifiedTime = new HashMap<>(); - private final Map> cachedZoomedSegments = new HashMap<>(); + private final Map segmentsCache = new HashMap<>(); private List cache = new ArrayList<>(); private Map pointFileMap = new HashMap<>(); @@ -688,7 +687,7 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM private void drawSelectedFileSegments(SelectedGpxFile selectedGpxFile, boolean currentTrack, Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { - boolean visible = QuadRect.trivialOverlap(tileBox.getLatLonBounds(), GPXUtilities.calculateTrackBounds(selectedGpxFile.getModifiablePointsToDisplay())); + boolean visible = QuadRect.trivialOverlap(tileBox.getLatLonBounds(), GPXUtilities.calculateTrackBounds(selectedGpxFile.getPointsToDisplay())); if (!selectedGpxFile.getGpxFile().hasTrkPt() || !visible) { return; } @@ -698,7 +697,7 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM List segments = new ArrayList<>(); if (scaleType == null) { - segments.addAll(selectedGpxFile.getModifiablePointsToDisplay()); + segments.addAll(selectedGpxFile.getPointsToDisplay()); } else { segments.addAll(getCachedSegments(selectedGpxFile, scaleType)); } @@ -728,69 +727,14 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM private List getCachedSegments(SelectedGpxFile selectedGpxFile, GradientScaleType scaleType) { GPXFile gpxFile = selectedGpxFile.getGpxFile(); - String fileName = gpxFile.path; - String trackId = view.getZoom() + "_" + scaleType.toString() + "_" + fileName; - - if (cachedModifiedTime.get(fileName) != null && cachedModifiedTime.get(fileName) == gpxFile.modifiedTime) { - List segments = cachedZoomedSegments.get(trackId); - if (segments == null) { - segments = calculateGradientTrack(selectedGpxFile, scaleType); - cachedZoomedSegments.put(trackId, segments); - } - return segments; - } else { - if (cachedModifiedTime.get(fileName) != null) { - for (String key : cachedZoomedSegments.keySet()) { - if (key.contains("_" + fileName)) { - cachedZoomedSegments.remove(key); - } - } - } - cachedModifiedTime.put(fileName, gpxFile.modifiedTime); - List segments = calculateGradientTrack(selectedGpxFile, scaleType); - cachedZoomedSegments.put(trackId, segments); - return segments; + String path = gpxFile.path; + long modifiedTime = gpxFile.modifiedTime; + CachedTrack cachedTrack = segmentsCache.get(path); + if (cachedTrack == null) { + cachedTrack = new CachedTrack(view.getApplication(), modifiedTime); + segmentsCache.put(path, cachedTrack); } - } - - private List calculateGradientTrack(SelectedGpxFile selectedGpxFile, GradientScaleType scaleType) { - OsmandApplication app = view.getApplication(); - GPXFile gpxFile = selectedGpxFile.getGpxFile(); - RouteColorize colorize = new RouteColorize(view.getZoom(), gpxFile, selectedGpxFile.getTrackAnalysis(app), - scaleType.toColorizationType(), app.getSettings().getApplicationMode().getMaxSpeed()); - colorize.setPalette(getColorizationPalette(gpxFile, scaleType)); - List colorsOfPoints = colorize.getResult(true); - return createSimplifiedSegmentsFromColorizedPoints(selectedGpxFile.getGpxFile(), colorsOfPoints, scaleType); - } - - private List createSimplifiedSegmentsFromColorizedPoints(GPXFile gpxFile, - List colorizationPoints, - GradientScaleType scaleType) { - List simplifiedSegments = new ArrayList<>(); - ColorizationType colorizationType = scaleType.toColorizationType(); - int id = 0; - int colorPointIdx = 0; - - for (GPXUtilities.Track track : gpxFile.tracks) { - for (TrkSegment segment : track.segments) { - TrkSegment simplifiedSegment = new TrkSegment(); - simplifiedSegments.add(simplifiedSegment); - for (WptPt pt : segment.points) { - if (colorPointIdx >= colorizationPoints.size()) { - return simplifiedSegments; - } - RouteColorizationPoint colorPoint = colorizationPoints.get(colorPointIdx); - if (colorPoint.id == id) { - simplifiedSegment.points.add(pt); - pt.setColor(colorizationType, colorPoint.color); - colorPointIdx++; - } - id++; - } - } - } - - return simplifiedSegments; + return cachedTrack.getCachedSegments(selectedGpxFile, view.getZoom(), scaleType, getColorizationPalette(gpxFile, scaleType)); } private float getTrackWidth(String width, float defaultTrackWidth) { @@ -1238,4 +1182,76 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM view.getApplication().getItineraryHelper().runSynchronization(group); } } + + private static class CachedTrack { + + private OsmandApplication app; + + private long modifiedTime; + private final Map> cache = new HashMap<>(); + + public CachedTrack(@NonNull OsmandApplication app, long modifiedTime) { + this.app = app; + this.modifiedTime = modifiedTime; + } + + public List getCachedSegments(@NonNull SelectedGpxFile selectedGpxFile, int zoom, + @NonNull GradientScaleType scaleType, + int[] gradientPalette) { + GPXFile gpxFile = selectedGpxFile.getGpxFile(); + String trackId = zoom + "_" + scaleType.toString(); + if (modifiedTime == gpxFile.modifiedTime) { + List segments = cache.get(trackId); + if (segments == null) { + segments = calculateGradientTrack(selectedGpxFile, zoom, scaleType, gradientPalette); + cache.put(trackId, segments); + } + return segments; + } else { + cache.clear(); + modifiedTime = gpxFile.modifiedTime; + List segments = calculateGradientTrack(selectedGpxFile, zoom, scaleType, gradientPalette); + cache.put(trackId, segments); + return segments; + } + } + + private List calculateGradientTrack(SelectedGpxFile selectedGpxFile, int zoom, + GradientScaleType scaleType, int[] gradientPalette) { + GPXFile gpxFile = selectedGpxFile.getGpxFile(); + RouteColorize colorize = new RouteColorize(zoom, gpxFile, selectedGpxFile.getTrackAnalysis(app), + scaleType.toColorizationType(), app.getSettings().getApplicationMode().getMaxSpeed()); + colorize.setPalette(gradientPalette); + List colorsOfPoints = colorize.getResult(true); + return createSimplifiedSegments(selectedGpxFile.getGpxFile(), colorsOfPoints, scaleType); + } + + private List createSimplifiedSegments(GPXFile gpxFile, + List colorizationPoints, + GradientScaleType scaleType) { + List simplifiedSegments = new ArrayList<>(); + ColorizationType colorizationType = scaleType.toColorizationType(); + int id = 0; + int colorPointIdx = 0; + + for (TrkSegment segment : gpxFile.getNonEmptyTrkSegments(false)) { + TrkSegment simplifiedSegment = new TrkSegment(); + simplifiedSegments.add(simplifiedSegment); + for (WptPt pt : segment.points) { + if (colorPointIdx >= colorizationPoints.size()) { + return simplifiedSegments; + } + RouteColorizationPoint colorPoint = colorizationPoints.get(colorPointIdx); + if (colorPoint.id == id) { + simplifiedSegment.points.add(pt); + pt.setColor(colorizationType, colorPoint.color); + colorPointIdx++; + } + id++; + } + } + + return simplifiedSegments; + } + } } \ No newline at end of file