From 3e13d309f6bd7dd16195bff72227a337ad1e04ed Mon Sep 17 00:00:00 2001 From: Andrew Davie Date: Sun, 3 Apr 2016 01:04:26 +1100 Subject: [PATCH] "Massive" optimisation - on-demand culling for GPX tracks A bit of inspiration; rather than trigger the culling for tracks when they are loaded and every time you zoom (i.e, creating the resampled or simplified tracks such as standard track (Ramer Douglas Peucer line reduction), arrows, speed, markers (all resample)... now the system uses the bounding rectangle of the original track to trigger the cull(s) only when the track is onscreen and viewable. This now allows dozens of tracks to be loaded without initial slowdown caused by all those resamplers and cullers running in the background. It's an on-demand system, in other words. So, whereas previously if you had 20 GPX tracks loaded, and you zoomed in/out, then all 20 of those tracks would start a Ramer-Douglas-Peucer resize, which would significantly slow down the whole thing (even though they are asynchronous they do take a toll on performance). Now, when you zoom in/out, only visible tracks (or ones which become visible) will start the RDP resize. --- OsmAnd/src/net/osmand/plus/GPXUtilities.java | 10 +- .../src/net/osmand/plus/views/GPXLayer.java | 14 +-- .../src/net/osmand/plus/views/Renderable.java | 100 +++++++++--------- 3 files changed, 57 insertions(+), 67 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/GPXUtilities.java b/OsmAnd/src/net/osmand/plus/GPXUtilities.java index c5d2f85679..ebb85a5a30 100644 --- a/OsmAnd/src/net/osmand/plus/GPXUtilities.java +++ b/OsmAnd/src/net/osmand/plus/GPXUtilities.java @@ -220,15 +220,9 @@ public class GPXUtilities { return convert(splitSegments); } - public void recalculateRenderScales(double zoom) { + public void drawRenderers(double zoom, Paint p, Canvas c, RotatedTileBox tb) { for (Renderable.RenderableSegment rs : renders) { - rs.recalculateRenderScale(zoom); - } - } - - public void drawRenderers(Paint p, Canvas c, RotatedTileBox tb) { - for (Renderable.RenderableSegment rs : renders) { - rs.drawSingleSegment(p, c, tb); + rs.drawSegment(zoom, p, c, tb); } } } diff --git a/OsmAnd/src/net/osmand/plus/views/GPXLayer.java b/OsmAnd/src/net/osmand/plus/views/GPXLayer.java index 5a111a7497..7345a693fa 100644 --- a/OsmAnd/src/net/osmand/plus/views/GPXLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/GPXLayer.java @@ -205,7 +205,7 @@ public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContex if(points != null) { updatePaints(0, false, false, settings, tileBox); for (TrkSegment ts : points) - ts.drawRenderers(paint, canvas,tileBox); + ts.drawRenderers(view.getZoom(), paint, canvas, tileBox); } else { List selectedGPXFiles = selectedGpxHelper.getSelectedGPXFiles(); cache.clear(); @@ -334,17 +334,17 @@ public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContex ts.renders.add(new Renderable.CurrentTrack(ts.points)); } else { ts.renders.add(new Renderable.Altitude(ts.points, 10)); - ts.renders.add(new Renderable.StandardTrack(ts.points, 17.5)); - ts.renders.add(new Renderable.DistanceMarker(ts.points, 1000)); - //ts.renders.add(new Renderable.Conveyor(ts.points, view, 20, 250)); - //ts.renders.add(new Renderable.Speed(ts.points, 50)); + ts.renders.add(new Renderable.StandardTrack(ts.points, 17.2)); + ts.renders.add(new Renderable.Conveyor(ts.points, view, 20, 250)); ts.renders.add(new Renderable.Arrows(ts.points, view, 40, 250)); + ts.renders.add(new Renderable.DistanceMarker(ts.points, 1000)); + //ts.renders.add(new Renderable.Speed(ts.points, 50)); } } - ts.recalculateRenderScales(view.getZoom()); + //ts.recalculateRenderScales(view.getZoom()); updatePaints(ts.getColor(cachedColor), g.isRoutePoints(), g.isShowCurrentTrack(), settings, tileBox); - ts.drawRenderers(paint, canvas, tileBox); + ts.drawRenderers(view.getZoom(), paint, canvas, tileBox); } } } diff --git a/OsmAnd/src/net/osmand/plus/views/Renderable.java b/OsmAnd/src/net/osmand/plus/views/Renderable.java index c833eb1af4..dd7b371118 100644 --- a/OsmAnd/src/net/osmand/plus/views/Renderable.java +++ b/OsmAnd/src/net/osmand/plus/views/Renderable.java @@ -50,13 +50,14 @@ public class Renderable { public List points = null; // Original list of points protected List culled = new ArrayList(); // Reduced/resampled list of points + protected int pointSize; protected QuadRect trackBounds; double zoom = -1; AsynchronousResampler culler = null; // The currently active resampler protected Paint paint = null; // MUST be set by 'updateLocalPaint' before use - public RenderableSegment(List pt) { + public RenderableSegment(List pt) { points = pt; calculateBounds(points); } @@ -72,8 +73,15 @@ public class Renderable { paint.setStrokeWidth(p.getStrokeWidth()); } - public void recalculateRenderScale(double zoom) {} - public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) {} + public void startCuller(double zoom) {} + protected void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {} + + public void drawSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { + if (QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) { // is visible? + startCuller(zoom); + drawSingleSegment(zoom, p, canvas, tileBox); + } + } private void calculateBounds(List pts) { trackBounds = new QuadRect(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, @@ -81,9 +89,9 @@ public class Renderable { updateBounds(pts, 0); } - public void updateBounds(List pts, int startIndex) { - int size = pts.size(); - for (int i = startIndex; i < size; i++) { + protected void updateBounds(List pts, int startIndex) { + pointSize = pts.size(); + for (int i = startIndex; i < pointSize; i++) { WptPt pt = pts.get(i); trackBounds.right = Math.max(trackBounds.right, pt.lon); trackBounds.left = Math.min(trackBounds.left, pt.lon); @@ -95,16 +103,15 @@ public class Renderable { // When the asynchronous task has finished, it calls this function to set the 'culled' list public void setRDP(List cull) { culled = cull; - //calculateBounds(culled); } protected void draw(List pts, Paint p, Canvas canvas, RotatedTileBox tileBox) { - if (pts.size() > 1 && QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) { + if (pts.size() > 1) { updateLocalPaint(p); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); - + float stroke = paint.getStrokeWidth() / 2; float clipL = -stroke; @@ -133,7 +140,6 @@ public class Renderable { canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); } } - } //---------------------------------------------------------------------------------------------- @@ -147,7 +153,7 @@ public class Renderable { this.base = base; } - @Override public void recalculateRenderScale(double newZoom) { + @Override public void startCuller(double newZoom) { // Here we create the 'shadow' resampled/culled points list, based on the asynchronous call. // The asynchronous callback will set the variable 'culled', and that is preferentially used for rendering @@ -170,7 +176,7 @@ public class Renderable { } } - @Override public void drawSingleSegment(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); } } @@ -186,18 +192,16 @@ public class Renderable { this.segmentSize = segmentSize; } - @Override public void recalculateRenderScale(double zoom) { + @Override public void startCuller(double zoom) { if (culler == null) { culler = new AsynchronousResampler.ResampleAltitude(this, segmentSize); // once only! culler.execute(""); } } - @Override public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) { - - if (culled.size() > 1 - && QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) { + @Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { + if (culled.size() > 1) { updateLocalPaint(p); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); @@ -241,7 +245,7 @@ public class Renderable { super(pt, segmentSize); } - @Override public void recalculateRenderScale(double zoom) { + @Override public void startCuller(double zoom) { if (culler == null) { culler = new AsynchronousResampler.ResampleSpeed(this, segmentSize); // once only! culler.execute(""); @@ -262,7 +266,7 @@ public class Renderable { Renderable.startScreenRefresh(view, refreshRate); } - @Override public void recalculateRenderScale(double zoom) { + @Override public void startCuller(double zoom) { if (culler == null) { culler = new AsynchronousResampler.GenericResampler(this, segmentSize); // once only! culler.execute(""); @@ -276,11 +280,9 @@ public class Renderable { return Color.HSVToColor(hsv); } - @Override public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) { - - if (culled.size() > 1 - && QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) { + @Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { + if (culled.size() > 1) { updateLocalPaint(p); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); @@ -331,8 +333,7 @@ public class Renderable { this.segmentSize = segmentSize; } - @Override public void recalculateRenderScale(double zoom) { - this.zoom = zoom; + @Override public void startCuller(double zoom) { if (culler == null) { culler = new AsynchronousResampler.GenericResampler(this, segmentSize); // once only culler.execute(""); @@ -345,18 +346,16 @@ public class Renderable { return lab; } - @Override public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) { - - if (!culled.isEmpty() && zoom > 12 - && QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) { + @Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { + if (zoom > 12 && !culled.isEmpty()) { updateLocalPaint(p); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); float scale = 50; float stroke = paint.getStrokeWidth(); - for (int i = culled.size()-1; --i >= 0;) { + for (int i = culled.size() - 1; --i >= 0; ) { WptPt pt = culled.get(i); float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon); @@ -369,17 +368,17 @@ public class Renderable { String lab = getKmLabel(pt.getDistance()); paint.getTextBounds(lab, 0, lab.length(), bounds); - int rectH = bounds.height(); - int rectW = bounds.width(); + int rectH = bounds.height(); + int rectW = bounds.width(); - if (x < canvas.getWidth() + rectW/2 + scale && x > -rectW/2 + scale - && y < canvas.getHeight() + rectH/2f && y > -rectH/2f) { + if (x < canvas.getWidth() + rectW / 2 + scale && x > -rectW / 2 + scale + && y < canvas.getHeight() + rectH / 2f && y > -rectH / 2f) { paint.setColor(Color.BLACK); paint.setStrokeWidth(stroke); canvas.drawPoint(x, y, paint); paint.setStrokeWidth(4); - canvas.drawText(lab,x-rectW/2+40,y+rectH/2,paint); + canvas.drawText(lab, x - rectW / 2 + 40, y + rectH / 2, paint); } canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); } @@ -401,17 +400,16 @@ public class Renderable { Renderable.startScreenRefresh(view, refreshRate); } - @Override public void recalculateRenderScale(double zoom) { - this.zoom = zoom; + @Override public void startCuller(double zoom) { if (culler == null) { culler = new AsynchronousResampler.GenericResampler(this, segmentSize); // once culler.execute(""); } } - private void drawArrows(Canvas canvas, RotatedTileBox tileBox, boolean internal) { + private void drawArrows(double zoom, Canvas canvas, RotatedTileBox tileBox, boolean internal) { - float scale = internal ? 0.8f : 1.0f; + float scale = internal ? 0.6f : 1.0f; float stroke = paint.getStrokeWidth(); double zoomlimit = zoom > 15 ? 15f : zoom; @@ -482,16 +480,14 @@ public class Renderable { paint.setStrokeWidth(stroke); } - @Override public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) { - - if (!culled.isEmpty() && zoom > 13 - && QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) { + @Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { + if (zoom > 13 && !culled.isEmpty()) { updateLocalPaint(p); canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); cachedC = conveyor; - drawArrows(canvas, tileBox, false); - drawArrows(canvas, tileBox, true); + drawArrows(zoom, canvas, tileBox, false); + drawArrows(zoom, canvas, tileBox, true); canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); } } @@ -501,18 +497,18 @@ public class Renderable { public static class CurrentTrack extends RenderableSegment { - private int size; - public CurrentTrack(List pt) { super(pt); - size = pt.size(); } - @Override public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) { - if (points.size() != size) { - updateBounds(points, size); // use newly added points to recalculate bounding box - size = points.size(); + @Override public void drawSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { + if (points.size() != pointSize) { + updateBounds(points, pointSize); } + drawSingleSegment(zoom, p, canvas, tileBox); + } + + @Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) { draw(points, p, canvas, tileBox); } }