Merge pull request #2413 from andrew-davie/minimal
GPX asynchronous line drawing - super quick MINIMAL code set
This commit is contained in:
commit
63ab190eb2
6 changed files with 336 additions and 82 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
80
OsmAnd/src/net/osmand/plus/views/AsynchronousResampler.java
Normal file
80
OsmAnd/src/net/osmand/plus/views/AsynchronousResampler.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 ;
|
||||
|
|
168
OsmAnd/src/net/osmand/plus/views/Renderable.java
Normal file
168
OsmAnd/src/net/osmand/plus/views/Renderable.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue