MINIMAL code set for GPX line drawing

Has all the structure of the classes needed for extension to arrows/markers/whatever.
However, this is just the minimal subset of those base classes required to implement the line drawing functionality
with the asynchronous resampling, and fully compatible with 'current track' which is NOT resampled in any way.

These files are a branch from the prior pull request, and hopefully will make everyone more comfortable about including
the code. The altitude and speed displays, all the bells and whistles - that can come later if needed.
This commit is contained in:
Andrew Davie 2016-04-04 00:30:07 +10:00
parent ceddb50e60
commit 9f0bcf3257
3 changed files with 7 additions and 515 deletions

View file

@ -3,7 +3,6 @@ package net.osmand.plus.views;
import android.os.AsyncTask;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
@ -26,157 +25,6 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
}
}
private WptPt createIntermediatePoint(WptPt lastPt, WptPt pt, double partial, double dist) {
WptPt newPt = new WptPt(
lastPt.getLatitude() + partial * (pt.getLatitude() - lastPt.getLatitude()),
lastPt.getLongitude() + partial * (pt.getLongitude() - lastPt.getLongitude()),
(long) (lastPt.time + partial * (pt.time - lastPt.time)),
lastPt.ele + partial * (pt.ele - lastPt.ele),
lastPt.speed + partial * (pt.speed - lastPt.speed),
lastPt.hdop + partial * (pt.hdop - lastPt.hdop));
newPt.setDistance(dist);
return newPt;
}
protected List<WptPt> resampleTrack(List<WptPt> pts, double dist) {
List<WptPt> newPts = new ArrayList<>();
int size = pts.size();
if (size > 0) {
WptPt lastPt = pts.get(0);
double segSub = 0;
double cumDist = 0;
for (int i = 1; i < size && !isCancelled(); i++) {
WptPt pt = pts.get(i);
double segLength = MapUtils.getDistance(pt.getLatitude(), pt.getLongitude(), lastPt.getLatitude(), lastPt.getLongitude());
while (segSub < segLength) {
double partial = segSub / segLength;
newPts.add(createIntermediatePoint(lastPt, pt, segSub/segLength, cumDist + segLength * partial));
segSub += dist;
}
segSub -= segLength;
cumDist += segLength;
lastPt = pt;
}
newPts.add(createIntermediatePoint(lastPt, lastPt, 0, cumDist));
}
return newPts;
}
//----------------------------------------------------------------------------------------------
public static class ResampleAltitude extends AsynchronousResampler {
private double segmentSize;
ResampleAltitude(Renderable.RenderableSegment rs, double segmentSize) {
super(rs);
this.segmentSize = segmentSize;
}
@Override protected String doInBackground(String... params) {
// Resample track, then analyse altitudes and set colours for each point
culled = resampleTrack(rs.points, segmentSize);
if (!isCancelled() && !culled.isEmpty()) {
int halfC = Algorithms.getRainbowColor(0.5);
// Calculate the absolutes of the altitude variations
Double max = culled.get(0).ele;
Double min = max;
for (WptPt pt : culled) {
max = Math.max(max, pt.ele);
min = Math.min(min, pt.ele);
pt.colourARGB = halfC; // default, in case there are no 'ele' in GPX
}
Double elevationRange = max - min;
if (elevationRange > 0)
for (WptPt pt : culled)
pt.colourARGB = Algorithms.getRainbowColor((pt.ele - min) / elevationRange);
}
return null;
}
}
//----------------------------------------------------------------------------------------------
public static class ResampleSpeed extends AsynchronousResampler {
private double segmentSize;
ResampleSpeed(Renderable.RenderableSegment rs, double segmentSize) {
super(rs);
this.segmentSize = segmentSize;
}
@Override protected String doInBackground(String... params) {
// Resample track, then analyse speeds and set colours for each point
culled = resampleTrack(rs.points, segmentSize);
if (!isCancelled() && !culled.isEmpty()) {
WptPt lastPt = culled.get(0);
lastPt.speed = 0;
int size = culled.size();
for (int i = 1; i < size; i++) {
WptPt pt = culled.get(i);
double delta = pt.time - lastPt.time;
pt.speed = delta > 0 ? MapUtils.getDistance(pt.getLatitude(), pt.getLongitude(),
lastPt.getLatitude(), lastPt.getLongitude()) / delta : 0;
lastPt = pt;
}
if (size > 1) {
culled.get(0).speed = culled.get(1).speed; // fixup 1st speed
}
double max = lastPt.speed;
double min = max;
int halfC = Algorithms.getRainbowColor(0.5);
for (WptPt pt : culled) {
max = Math.max(max, pt.speed);
min = Math.min(min, pt.speed);
pt.colourARGB = halfC; // default, in case there are no 'time' in GPX
}
double speedRange = max - min;
if (speedRange > 0) {
for (WptPt pt : culled)
pt.colourARGB = Algorithms.getRainbowColor((pt.speed - min) / speedRange);
}
}
return null;
}
}
//----------------------------------------------------------------------------------------------
public static class GenericResampler extends AsynchronousResampler {
private double segmentSize;
public GenericResampler(Renderable.RenderableSegment rs, double segmentSize) {
super(rs);
this.segmentSize = segmentSize;
}
@Override protected String doInBackground(String... params) {
culled = resampleTrack(rs.points, segmentSize);
return null;
}
}
//----------------------------------------------------------------------------------------------
public static class RamerDouglasPeucer extends AsynchronousResampler {
private double epsilon;
@ -228,9 +76,5 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
survivor[end] = true;
}
}
}
}

View file

@ -334,16 +334,10 @@ public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContex
if (g.isShowCurrentTrack()) {
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.2));
//ts.renders.add(new Renderable.Conveyor(ts.points, view, 5, 250));
//ts.renders.add(new Renderable.Arrows(ts.points, view, 10, 250));
//ts.renders.add(new Renderable.DistanceMarker(ts.points, 1000));
//ts.renders.add(new Renderable.Speed(ts.points, 50));
}
}
//ts.recalculateRenderScales(view.getZoom());
updatePaints(ts.getColor(cachedColor), g.isRoutePoints(), g.isShowCurrentTrack(), settings, tileBox);
ts.drawRenderers(view.getZoom(), paint, canvas, tileBox);
}

View file

@ -1,51 +1,18 @@
package net.osmand.plus.views;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class Renderable {
// This class handles the actual drawing of segment 'layers'. A segment is a piece of track
// (i.e., a list of WptPt) which has renders attached to it. There can be any number of renders
// layered upon each other to give multiple effects.
static private Timer t = null; // fires a repaint for animating segments
static private int conveyor = 0; // single cycler for 'conveyor' style renders
static private OsmandMapTileView view = null; // for paint refresh
// If any render wants to have animation, something needs to make a one-off call to 'startScreenRefresh'
// to setup a timer to periodically force a screen refresh/redraw
public static void startScreenRefresh(OsmandMapTileView v, long period) {
view = v;
if (t==null && v != null) {
t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
public void run() {
conveyor++;
view.refreshMap();
}
}, 0, period);
}
}
//----------------------------------------------------------------------------------------------
public static abstract class RenderableSegment {
public List<WptPt> points = null; // Original list of points
@ -75,23 +42,7 @@ public class Renderable {
paint.setStrokeWidth(p.getStrokeWidth());
}
protected void startCuller(double newZoom) {
if (zoom != newZoom) {
if (culler != null) {
culler.cancel(true);
}
culled.clear();
zoom = newZoom;
double length = Math.pow(2.0, 17 - zoom) * segmentSize;
culler = getCuller(length);
culler.execute("");
}
}
protected AsynchronousResampler getCuller(double segmentSize) {
return new AsynchronousResampler.GenericResampler(this, segmentSize);
}
protected abstract void startCuller(double newZoom);
protected void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {}
@ -119,7 +70,6 @@ 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;
}
@ -133,9 +83,9 @@ public class Renderable {
QuadRect tileBounds = tileBox.getLatLonBounds();
WptPt lastPt = pts.get(0);
float lastx = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
float lasty = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
boolean last = false;
float lastx = 0;
float lasty = 0;
boolean reCalculateLastXY = true;
int size = pts.size();
for (int i = 1; i < size; i++) {
@ -144,10 +94,10 @@ public class Renderable {
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 (!last) {
if (reCalculateLastXY) {
lastx = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
lasty = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
last = true;
reCalculateLastXY = false;
}
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
@ -159,7 +109,7 @@ public class Renderable {
lasty = y;
} else {
last = false;
reCalculateLastXY = true;
}
lastPt = pt;
}
@ -168,8 +118,6 @@ public class Renderable {
}
}
//----------------------------------------------------------------------------------------------
public static class StandardTrack extends RenderableSegment {
public StandardTrack(List<WptPt> pt, double base) {
@ -198,299 +146,6 @@ public class Renderable {
}
}
//----------------------------------------------------------------------------------------------
public static class Altitude extends RenderableSegment {
public Altitude(List<WptPt> pt, double segmentSize) {
super(pt, segmentSize);
}
@Override protected AsynchronousResampler getCuller(double segmentSize) {
return new AsynchronousResampler.ResampleAltitude(this, segmentSize);
}
@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());
float bandWidth = paint.getStrokeWidth() * 3;
paint.setStrokeWidth(bandWidth);
QuadRect tileBounds = tileBox.getLatLonBounds();
WptPt lastPt = culled.get(0);
float lastx = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
float lasty = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
boolean last = false;
int size = culled.size();
for (int i = 1; i < size; i++) {
WptPt pt = culled.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 (!last) {
lastx = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
lasty = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
last = true;
}
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
paint.setColor(pt.colourARGB);
canvas.drawLine(lastx, lasty, x, y, paint);
lastx = x;
lasty = y;
} else {
last = false;
}
lastPt = pt;
}
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
}
}
}
//----------------------------------------------------------------------------------------------
public static class Speed extends Altitude {
public Speed(List<WptPt> pt, double segmentSize) {
super(pt, segmentSize);
}
@Override protected AsynchronousResampler getCuller(double segmentSize) {
return new AsynchronousResampler.ResampleSpeed(this, segmentSize);
}
}
//----------------------------------------------------------------------------------------------
public static class Conveyor extends RenderableSegment {
public Conveyor(List<WptPt> pt, OsmandMapTileView view, double segmentSize, long refreshRate) {
super(pt, segmentSize);
Renderable.startScreenRefresh(view, refreshRate);
}
private int getComplementaryColor(int colorToInvert) {
float[] hsv = new float[3];
Color.RGBToHSV(Color.red(colorToInvert), Color.green(colorToInvert), Color.blue(colorToInvert), hsv);
hsv[0] = (hsv[0] + 180) % 360;
return Color.HSVToColor(hsv);
}
@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());
paint.setColor(getComplementaryColor(p.getColor()));
QuadRect tileBounds = tileBox.getLatLonBounds();
WptPt lastPt = culled.get(0);
float lastx = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
float lasty = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
boolean last = false;
int intp = conveyor;
int size = culled.size();
for (int i = 1; i < size; i++, intp--) {
WptPt pt = culled.get(i);
if ((intp & 7) < 3
&& 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 (!last) {
lastx = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
lasty = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
last = true;
}
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 {
last = false;
}
lastPt = pt;
}
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
}
}
}
//----------------------------------------------------------------------------------------------
public static class DistanceMarker extends RenderableSegment {
public DistanceMarker(List<WptPt> pt, double segmentSize) {
super(pt, segmentSize);
}
@Override public void startCuller(double zoom) {
if (culler == null) {
culler = new AsynchronousResampler.GenericResampler(this, segmentSize); // once only
culler.execute("");
}
}
private String getKmLabel(double value) {
String lab;
lab = String.format("%d",(int)((value+0.5)/1000));
return lab;
}
@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; ) {
WptPt pt = culled.get(i);
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
paint.setTextSize(scale);
paint.setStrokeWidth(3);
Rect bounds = new Rect();
String lab = getKmLabel(pt.getDistance());
paint.getTextBounds(lab, 0, lab.length(), bounds);
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) {
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.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
}
}
}
}
//----------------------------------------------------------------------------------------------
public static class Arrows extends RenderableSegment {
public Arrows(List<WptPt> pt, OsmandMapTileView view, double segmentSize, long refreshRate) {
super(pt, segmentSize);
Renderable.startScreenRefresh(view, refreshRate);
}
private void drawArrows(int cachedC, Canvas canvas, RotatedTileBox tileBox, boolean internal) {
float scale = internal ? 0.6f : 1.0f;
float stroke = paint.getStrokeWidth();
float arrowSize = 75f;
boolean broken = true;
int intp = cachedC; // the segment cycler
float clipL = -arrowSize;
float clipB = -arrowSize;
float clipT = canvas.getHeight() + arrowSize;
float clipR = canvas.getWidth() + arrowSize;
float lastx = 0;
float lasty = Float.NEGATIVE_INFINITY;
int size = culled.size();
for (int i = 0; i < size; i++, intp--) {
WptPt pt = culled.get(i);
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
boolean nextBroken = true;
if (Math.min(x, lastx) < clipR && Math.max(x, lastx) > clipL
&& Math.min(y, lasty) < clipT && Math.max(y, lasty) > clipB) {
float segment = intp & 15;
if (segment < 5) {
paint.setColor(internal ? Algorithms.getRainbowColor(((double) (i)) / ((double) size)) : Color.BLACK);
float segpiece = 5 - segment;
if (segpiece > 3)
segpiece = 3;
if (!broken) {
float sw = stroke * segpiece * scale;
paint.setStrokeWidth(sw);
canvas.drawLine(lastx, lasty, x, y, paint);
}
nextBroken = false;
// arrowhead...
if (segment == 0 && lasty != Float.NEGATIVE_INFINITY) {
float sw = stroke * segpiece * scale;
paint.setStrokeWidth(sw);
double angle = Math.atan2(lasty - y, lastx - x);
float extendx = x - (float) Math.cos(angle) * arrowSize / 2;
float extendy = y - (float) Math.sin(angle) * arrowSize / 2;
float newx1 = extendx + (float) Math.cos(angle - 0.4) * arrowSize;
float newy1 = extendy + (float) Math.sin(angle - 0.4) * arrowSize;
float newx2 = extendx + (float) Math.cos(angle + 0.4) * arrowSize;
float newy2 = extendy + (float) Math.sin(angle + 0.4) * arrowSize;
canvas.drawLine(extendx, extendy, x, y, paint);
canvas.drawLine(newx1, newy1, extendx, extendy, paint);
canvas.drawLine(newx2, newy2, extendx, extendy, paint);
}
}
}
broken = nextBroken;
lastx = x;
lasty = y;
}
paint.setStrokeWidth(stroke);
}
@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());
int cachedC = conveyor;
drawArrows(cachedC, canvas, tileBox, false);
drawArrows(cachedC, canvas, tileBox, true);
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
}
}
}
//----------------------------------------------------------------------------------------------
public static class CurrentTrack extends RenderableSegment {
public CurrentTrack(List<WptPt> pt) {
@ -510,5 +165,4 @@ public class Renderable {
draw(points, p, canvas, tileBox);
}
}
}