"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.
This commit is contained in:
Andrew Davie 2016-04-03 01:04:26 +11:00
parent 66128c8c66
commit 3e13d309f6
3 changed files with 57 additions and 67 deletions

View file

@ -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);
}
}
}

View file

@ -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<SelectedGpxFile> 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);
}
}
}

View file

@ -50,6 +50,7 @@ public class Renderable {
public List<WptPt> points = null; // Original list of points
protected List<WptPt> culled = new ArrayList(); // Reduced/resampled list of points
protected int pointSize;
protected QuadRect trackBounds;
double zoom = -1;
@ -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<WptPt> pts) {
trackBounds = new QuadRect(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,
@ -81,9 +89,9 @@ public class Renderable {
updateBounds(pts, 0);
}
public void updateBounds(List<WptPt> pts, int startIndex) {
int size = pts.size();
for (int i = startIndex; i < size; i++) {
protected void updateBounds(List<WptPt> 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,12 +103,11 @@ public class Renderable {
// When the asynchronous task has finished, it calls this function to set the 'culled' list
public void setRDP(List<WptPt> cull) {
culled = cull;
//calculateBounds(culled);
}
protected void draw(List<WptPt> 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());
@ -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,11 +346,9 @@ 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());
@ -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<WptPt> 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);
}
}