Merge pull request #2413 from andrew-davie/minimal

GPX asynchronous line drawing - super quick MINIMAL code set
This commit is contained in:
vshcherb 2016-04-04 17:21:00 +03:00
commit 63ab190eb2
6 changed files with 336 additions and 82 deletions

View file

@ -41,6 +41,10 @@ public class QuadRect {
return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
}
public static boolean trivialOverlap(QuadRect a, QuadRect b) {
return !((a.right < b.left) || (a.left > b.right) || (a.top < b.bottom) || (a.bottom > b.top));
}
public double centerX() {
return (left + right) / 2;
}

View file

@ -570,4 +570,23 @@ public class Algorithms {
}
return hexString;
}
public static int getRainbowColor(double percent) {
// Given an input percentage (0.0-1.0) this will produce a colour from a "wide rainbow"
// from purple (low) to red(high). This is useful for producing value-based colourations (e.g., altitude)
double a = (1. - percent) * 5.;
int X = (int)Math.floor(a);
int Y = (int)(Math.floor(255 * (a - X)));
switch (X) {
case 0: return 0xFFFF0000 + (Y<<8);
case 1: return 0xFF00FF00 + ((255-Y)<<16);
case 2: return 0xFF00FF00 + Y;
case 3: return 0xFF0000FF + ((255-Y)<<8);
case 4: return 0xFF0000FF + (Y << 16);
}
return 0xFFFF00FF;
}
}

View file

@ -2,12 +2,17 @@
package net.osmand.plus;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import net.osmand.Location;
import net.osmand.PlatformUtil;
import net.osmand.data.LocationPoint;
import net.osmand.data.PointDescription;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.Renderable;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
@ -98,10 +103,41 @@ public class GPXUtilities {
public double speed = 0;
public double hdop = Double.NaN;
public boolean deleted = false;
public int colourARGB = 0; // point colour (used for altitude/speed colouring)
public double distance = 0.0; // cumulative distance, if in a track
public WptPt() {
}
// public WptPt(WptPt toCopy) {
// this.lat = toCopy.lat;
// this.lon = toCopy.lon;
// if (toCopy.name != null) {
// this.name = new String(toCopy.name);
// }
// if (toCopy.link != null) {
// this.link = new String(toCopy.link);
// }
// if (toCopy.category != null) {
// this.category = new String(toCopy.category);
// }
// this.time = toCopy.time;
// this.ele = toCopy.ele;
// this.speed = toCopy.speed;
// this.hdop = toCopy.hdop;
// this.deleted = toCopy.deleted;
// this.colourARGB = toCopy.colourARGB;
// this.distance = toCopy.distance;
// }
public void setDistance(double dist) {
distance = dist;
}
public double getDistance() {
return distance;
}
@Override
public int getColor() {
return getColor(0);
@ -166,6 +202,9 @@ public class GPXUtilities {
public static class TrkSegment extends GPXExtensions {
public List<WptPt> points = new ArrayList<WptPt>();
private OsmandMapTileView view;
public List<Renderable.RenderableSegment> renders = new ArrayList<>();
public List<GPXTrackAnalysis> splitByDistance(double meters) {
return split(getDistanceMetric(), getTimeSplit(), meters);
@ -181,6 +220,11 @@ public class GPXUtilities {
return convert(splitSegments);
}
public void drawRenderers(double zoom, Paint p, Canvas c, RotatedTileBox tb) {
for (Renderable.RenderableSegment rs : renders) {
rs.drawSegment(zoom, p, c, tb);
}
}
}
public static class Track extends GPXExtensions {

View file

@ -0,0 +1,80 @@
package net.osmand.plus.views;
import android.os.AsyncTask;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
import java.util.List;
public abstract class AsynchronousResampler extends AsyncTask<String,Integer,String> {
protected Renderable.RenderableSegment rs;
protected List<WptPt> culled = null;
AsynchronousResampler(Renderable.RenderableSegment rs) {
assert rs != null;
assert rs.points != null;
this.rs = rs;
}
@Override protected void onPostExecute(String result) {
if (!isCancelled()) {
rs.setRDP(culled);
}
}
public static class RamerDouglasPeucer extends AsynchronousResampler {
private double epsilon;
public RamerDouglasPeucer(Renderable.RenderableSegment rs, double epsilon) {
super(rs);
this.epsilon = epsilon;
}
@Override protected String doInBackground(String... params) {
int nsize = rs.points.size();
if (nsize > 0) {
boolean survivor[] = new boolean[nsize];
cullRamerDouglasPeucer(survivor, 0, nsize - 1);
if (!isCancelled()) {
culled = new ArrayList<>();
survivor[0] = true;
for (int i = 0; i < nsize; i++) {
if (survivor[i]) {
culled.add(rs.points.get(i));
}
}
}
}
return null;
}
private void cullRamerDouglasPeucer(boolean survivor[], int start, int end) {
double dmax = Double.NEGATIVE_INFINITY;
int index = -1;
WptPt startPt = rs.points.get(start);
WptPt endPt = rs.points.get(end);
for (int i = start + 1; i < end && !isCancelled(); i++) {
WptPt pt = rs.points.get(i);
double d = MapUtils.getOrthogonalDistance(pt.lat, pt.lon, startPt.lat, startPt.lon, endPt.lat, endPt.lon);
if (d > dmax) {
dmax = d;
index = i;
}
}
if (dmax > epsilon) {
cullRamerDouglasPeucer(survivor, start, index);
cullRamerDouglasPeucer(survivor, index, end);
} else {
survivor[end] = true;
}
}
}
}

View file

@ -8,7 +8,6 @@ import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
@ -40,8 +39,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import gnu.trove.list.array.TIntArrayList;
public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContextMenuProvider,
MapTextProvider<WptPt> {
@ -207,7 +204,8 @@ public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContex
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
if(points != null) {
updatePaints(0, false, false, settings, tileBox);
drawSegments(canvas, tileBox, points);
for (TrkSegment ts : points)
ts.drawRenderers(view.getZoom(), paint, canvas, tileBox);
} else {
List<SelectedGpxFile> selectedGPXFiles = selectedGpxHelper.getSelectedGPXFiles();
cache.clear();
@ -325,11 +323,24 @@ public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContex
private void drawSelectedFilesSegments(Canvas canvas, RotatedTileBox tileBox,
List<SelectedGpxFile> selectedGPXFiles, DrawSettings settings) {
for (SelectedGpxFile g : selectedGPXFiles) {
List<TrkSegment> points = g.getPointsToDisplay();
boolean routePoints = g.isRoutePoints();
updatePaints(g.getColor(), routePoints, g.isShowCurrentTrack(), settings, tileBox);
drawSegments(canvas, tileBox, points);
List<TrkSegment> segments = g.getPointsToDisplay();
for (TrkSegment ts : segments) {
if (ts.renders.isEmpty() // only do once (CODE HERE NEEDS TO BE UI INSTEAD)
&& !ts.points.isEmpty()) { // hmmm. 0-point tracks happen, but.... how?
if (g.isShowCurrentTrack()) {
ts.renders.add(new Renderable.CurrentTrack(ts.points));
} else {
ts.renders.add(new Renderable.StandardTrack(ts.points, 17.2));
}
}
updatePaints(ts.getColor(cachedColor), g.isRoutePoints(), g.isShowCurrentTrack(), settings, tileBox);
ts.drawRenderers(view.getZoom(), paint, canvas, tileBox);
}
}
}
@ -352,83 +363,11 @@ public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContex
return pts;
}
private void drawSegments(Canvas canvas, RotatedTileBox tileBox, List<TrkSegment> points) {
final QuadRect latLonBounds = tileBox.getLatLonBounds();
for (TrkSegment l : points) {
int startIndex = -1;
int endIndex = -1;
int prevCross = 0;
double shift = 0;
for (int i = 0; i < l.points.size(); i++) {
WptPt ls = l.points.get(i);
int cross = 0;
cross |= (ls.lon < latLonBounds.left - shift ? 1 : 0);
cross |= (ls.lon > latLonBounds.right + shift ? 2 : 0);
cross |= (ls.lat > latLonBounds.top + shift ? 4 : 0);
cross |= (ls.lat < latLonBounds.bottom - shift ? 8 : 0);
if (i > 0) {
if ((prevCross & cross) == 0) {
if (endIndex == i - 1 && startIndex != -1) {
// continue previous line
} else {
// start new segment
if (startIndex >= 0) {
drawSegment(canvas, tileBox, l, startIndex, endIndex);
}
startIndex = i - 1;
}
endIndex = i;
}
}
prevCross = cross;
}
if (startIndex != -1) {
drawSegment(canvas, tileBox, l, startIndex, endIndex);
}
}
}
@Override
public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
}
private void drawSegment(Canvas canvas, RotatedTileBox tb, TrkSegment l, int startIndex, int endIndex) {
TIntArrayList tx = new TIntArrayList();
TIntArrayList ty = new TIntArrayList();
canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
Path path = new Path();
for (int i = startIndex; i <= endIndex; i++) {
WptPt p = l.points.get(i);
int x = (int) tb.getPixXFromLatLon(p.lat, p.lon);
int y = (int) tb.getPixYFromLatLon(p.lat, p.lon);
// int x = tb.getPixXFromLonNoRot(p.lon);
// int y = tb.getPixYFromLatNoRot(p.lat);
tx.add(x);
ty.add(y);
}
calculatePath(tb, tx, ty, path);
if(isPaint_1) {
canvas.drawPath(path, paint_1);
}
if(isShadowPaint) {
canvas.drawPath(path, shadowPaint);
}
int clr = paint.getColor();
if(clr != l.getColor(clr) && l.getColor(clr) != 0) {
paint.setColor(l.getColor(clr));
}
canvas.drawPath(path, paint);
paint.setColor(clr);
if(isPaint2) {
canvas.drawPath(path, paint2);
}
canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
}
private boolean calculateBelongs(int ex, int ey, int objx, int objy, int radius) {
return (Math.abs(objx - ex) <= radius * 2 && Math.abs(objy - ey) <= radius * 2) ;
// return Math.abs(objx - ex) <= radius && (ey - objy) <= radius / 2 && (objy - ey) <= 3 * radius ;

View file

@ -0,0 +1,168 @@
package net.osmand.plus.views;
import android.graphics.Canvas;
import android.graphics.Paint;
import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.GPXUtilities.WptPt;
import java.util.ArrayList;
import java.util.List;
public class Renderable {
public static abstract class RenderableSegment {
public List<WptPt> points = null; // Original list of points
protected List<WptPt> culled = new ArrayList<>(); // Reduced/resampled list of points
protected int pointSize;
protected double segmentSize;
protected QuadRect trackBounds;
protected double zoom = -1;
protected AsynchronousResampler culler = null; // The currently active resampler
protected Paint paint = null; // MUST be set by 'updateLocalPaint' before use
public RenderableSegment(List <WptPt> points, double segmentSize) {
this.points = points;
calculateBounds(points);
this.segmentSize = segmentSize;
}
protected void updateLocalPaint(Paint p) {
if (paint == null) {
paint = new Paint(p);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStyle(Paint.Style.FILL);
}
paint.setColor(p.getColor());
paint.setStrokeWidth(p.getStrokeWidth());
}
protected abstract void startCuller(double newZoom);
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,
Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
updateBounds(pts, 0);
}
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);
trackBounds.top = Math.max(trackBounds.top, pt.lat);
trackBounds.bottom = Math.min(trackBounds.bottom, pt.lat);
}
}
public void setRDP(List<WptPt> cull) {
culled = cull;
}
protected void draw(List<WptPt> pts, Paint p, Canvas canvas, RotatedTileBox tileBox) {
if (pts.size() > 1) {
updateLocalPaint(p);
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
QuadRect tileBounds = tileBox.getLatLonBounds();
WptPt lastPt = pts.get(0);
float lastx = 0;
float lasty = 0;
boolean reCalculateLastXY = true;
int size = pts.size();
for (int i = 1; i < size; i++) {
WptPt pt = pts.get(i);
if (Math.min(pt.lon, lastPt.lon) < tileBounds.right && Math.max(pt.lon, lastPt.lon) > tileBounds.left
&& Math.min(pt.lat, lastPt.lat) < tileBounds.top && Math.max(pt.lat, lastPt.lat) > tileBounds.bottom) {
if (reCalculateLastXY) {
lastx = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
lasty = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
reCalculateLastXY = false;
}
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
canvas.drawLine(lastx, lasty, x, y, paint);
lastx = x;
lasty = y;
} else {
reCalculateLastXY = true;
}
lastPt = pt;
}
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
}
}
}
public static class StandardTrack extends RenderableSegment {
public StandardTrack(List<WptPt> pt, double base) {
super(pt, base);
}
@Override public void startCuller(double newZoom) {
if (zoom != newZoom) {
if (culler != null) {
culler.cancel(true);
}
if (zoom < newZoom) { // if line would look worse (we're zooming in) then...
culled.clear(); // use full-resolution until re-cull complete
}
zoom = newZoom;
double cullDistance = Math.pow(2.0, segmentSize - zoom); // segmentSize == epsilon
culler = new AsynchronousResampler.RamerDouglasPeucer(this, cullDistance);
culler.execute("");
}
}
@Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {
draw(culled.isEmpty() ? points : culled, p, canvas, tileBox);
}
}
public static class CurrentTrack extends RenderableSegment {
public CurrentTrack(List<WptPt> pt) {
super(pt, 0);
}
@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 protected void startCuller(double newZoom) {}
@Override public void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {
draw(points, p, canvas, tileBox);
}
}
}