"Trivial" area bounds for segments for super-quick culling during draw.
Reworked all the renderers to be much more object oriented. Got the sample arrow and conveyor renderers working Tracked down a few bugs - particularly one still there - TrkSegment with 0 points! This in my opinion should never happen - it's coming in from "outside" to my code.
This commit is contained in:
parent
c8a245d031
commit
63ff22b6ab
6 changed files with 267 additions and 313 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -207,54 +207,16 @@ public class GPXUtilities {
|
|||
return convert(splitSegments);
|
||||
}
|
||||
|
||||
|
||||
// Track segments are drawn by 'Renderable' objects. A segment can have zero or more renderables,
|
||||
// each of which performs its own point-culling/reduction and display drawing. There are a selction
|
||||
// of renderable types, as defiend in Renderable.RenderType. To use, call this function to create
|
||||
// a new renderable type, and then attach it to your displayable object in a list or similar.
|
||||
|
||||
// The two parameters' maning varies based upon the type of renderable - see the parameters' usage
|
||||
// in each derived renderable class.
|
||||
|
||||
/* public Renderable.RenderableSegment addRenderable(OsmandMapTileView view, Renderable.RenderType type,
|
||||
double param1, double param2) {
|
||||
Renderable.RenderableSegment rs = null;
|
||||
switch (type) {
|
||||
case ORIGINAL: // a Ramer-Douglas-Peucer line reduction draw
|
||||
rs = new Renderable.RenderableSegment(type, points, param1, param2);
|
||||
break;
|
||||
case DISTANCE_MARKERS: // a resample every N metres draw
|
||||
rs = new Renderable.DistanceMarker(type, points, param1, param2);
|
||||
break;
|
||||
case CONVEYOR: // an animating segment draw
|
||||
rs = new Renderable.Conveyor(type, points, param1, param2);
|
||||
Renderable.startScreenRefresh(view, (long) param2);
|
||||
break;
|
||||
case ALTITUDE: // a colour-banded altitude draw
|
||||
rs = new Renderable.AltitudeColours(type, points, param1, param2);
|
||||
break;
|
||||
case SPEED: // a colour-banded speed draw
|
||||
rs = new Renderable.SpeedColours(type, points, param1, param2);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
public void recalculateRenderScales(double zoom) {
|
||||
for (Renderable.RenderableSegment rs : renders) {
|
||||
rs.recalculateRenderScale(zoom);
|
||||
}
|
||||
if (rs != null)
|
||||
renders.add(rs);
|
||||
return rs;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
public void recalculateRenderScales(OsmandMapTileView view) {
|
||||
for (Renderable.RenderableSegment rs : renders)
|
||||
rs.recalculateRenderScale(view);
|
||||
}
|
||||
|
||||
public void drawRenderers(Paint p, Canvas c, RotatedTileBox tb) {
|
||||
for (Renderable.RenderableSegment rs : renders)
|
||||
for (Renderable.RenderableSegment rs : renders) {
|
||||
rs.drawSingleSegment(p, c, tb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.osmand.plus.views;
|
|||
import android.os.AsyncTask;
|
||||
|
||||
import net.osmand.plus.GPXUtilities;
|
||||
import net.osmand.util.Algorithms;
|
||||
import net.osmand.util.MapUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -20,29 +21,11 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
|
|||
|
||||
@Override protected void onPostExecute(String result) {
|
||||
// executes on the UI thread so it's OK to change its variables
|
||||
if (rs != null && result.equals("OK") && !isCancelled())
|
||||
if (rs != null && result.equals("OK") && !isCancelled()) {
|
||||
rs.setRDP(culled);
|
||||
}
|
||||
|
||||
protected int getRainbowColour(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;
|
||||
}
|
||||
|
||||
|
||||
// Resample a list of points into a new list of points.
|
||||
// The new list is evenly-spaced (dist) and contains the first and last point from the original list.
|
||||
// The purpose is to allow tracks to be displayed with colours/shades/animation with even spacing
|
||||
|
@ -52,18 +35,19 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
|
|||
|
||||
protected List<GPXUtilities.WptPt> resampleTrack(List<GPXUtilities.WptPt> pts, double dist) {
|
||||
|
||||
ArrayList<GPXUtilities.WptPt> newPts = new ArrayList<GPXUtilities.WptPt>();
|
||||
ArrayList<GPXUtilities.WptPt> newPts = new ArrayList<>();
|
||||
|
||||
int ptCt = pts.size();
|
||||
if (pts != null && ptCt > 0) {
|
||||
if (ptCt > 0) {
|
||||
|
||||
GPXUtilities.WptPt lastPt = pts.get(0);
|
||||
double segSub = 0;
|
||||
double cumDist = 0;
|
||||
for (int i = 1; i < ptCt; i++) {
|
||||
|
||||
if (isCancelled())
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GPXUtilities.WptPt pt = pts.get(i);
|
||||
double segLength = MapUtils.getDistance(pt.getLatitude(), pt.getLongitude(), lastPt.getLatitude(), lastPt.getLongitude());
|
||||
|
@ -116,7 +100,7 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
|
|||
culled = resampleTrack(rs.getPoints(), segmentSize);
|
||||
if (!isCancelled()) {
|
||||
|
||||
int halfC = getRainbowColour(0.5); // default coloration if no elevations found
|
||||
int halfC = Algorithms.getRainbowColor(0.5);
|
||||
|
||||
// Calculate the absolutes of the altitude variations
|
||||
Double max = Double.NEGATIVE_INFINITY;
|
||||
|
@ -124,13 +108,13 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
|
|||
for (GPXUtilities.WptPt pt : culled) {
|
||||
max = Math.max(max, pt.ele);
|
||||
min = Math.min(min, pt.ele);
|
||||
pt.colourARGB = halfC;
|
||||
pt.colourARGB = halfC; // default, in case there are no 'ele' in GPX
|
||||
}
|
||||
|
||||
Double elevationRange = max - min;
|
||||
if (elevationRange > 0)
|
||||
for (GPXUtilities.WptPt pt : culled)
|
||||
pt.colourARGB = getRainbowColour((pt.ele - min) / elevationRange);
|
||||
pt.colourARGB = Algorithms.getRainbowColor((pt.ele - min) / elevationRange);
|
||||
}
|
||||
|
||||
return isCancelled() ? "" : "OK";
|
||||
|
@ -163,28 +147,31 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
|
|||
for (int i = 1; i < points.size(); i++) {
|
||||
GPXUtilities.WptPt pt = points.get(i);
|
||||
double delta = pt.time - lastPt.time;
|
||||
if (delta > 0)
|
||||
if (delta > 0) {
|
||||
pt.speed = MapUtils.getDistance(pt.getLatitude(), pt.getLongitude(),
|
||||
lastPt.getLatitude(), lastPt.getLongitude()) / delta;
|
||||
else
|
||||
} else {
|
||||
pt.speed = 0; // GPX doesn't have time - this is OK, colour will be mid-range for whole track
|
||||
}
|
||||
lastPt = pt;
|
||||
}
|
||||
|
||||
int halfC = Algorithms.getRainbowColor(0.5);
|
||||
|
||||
// Calculate the absolutes of the speed variations
|
||||
Double max = Double.NEGATIVE_INFINITY;
|
||||
Double min = Double.POSITIVE_INFINITY;
|
||||
for (GPXUtilities.WptPt pt : points) {
|
||||
max = Math.max(max, pt.speed);
|
||||
min = Math.min(min, pt.speed);
|
||||
pt.colourARGB = getRainbowColour(0.5);
|
||||
pt.colourARGB = halfC; // default, in case there are no 'time' in GPX
|
||||
}
|
||||
Double range = max - min;
|
||||
if (range > 0)
|
||||
Double speedRange = max - min;
|
||||
if (speedRange > 0) {
|
||||
for (GPXUtilities.WptPt pt : points)
|
||||
pt.colourARGB = getRainbowColour((pt.speed - min) / range);
|
||||
pt.colourARGB = Algorithms.getRainbowColor((pt.speed - min) / speedRange);
|
||||
}
|
||||
}
|
||||
|
||||
return isCancelled() ? "" : "OK";
|
||||
}
|
||||
}
|
||||
|
@ -221,9 +208,7 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
|
|||
|
||||
@Override protected String doInBackground(String... params) {
|
||||
|
||||
// Reduce the point-count of the GPX track. The concept is that at arbitrary scales, some points are superfluous.
|
||||
// This is handled using the well-known 'Ramer-Douglas-Peucker' algorithm. This code is modified from the similar code elsewhere
|
||||
// but optimised for this specific usage.
|
||||
// Reduce the point-count of the GPX track using Ramer-Douglas-Peucker algorithm.
|
||||
|
||||
points = rs.getPoints();
|
||||
culled = new ArrayList<>();
|
||||
|
@ -235,8 +220,9 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
|
|||
if (!isCancelled()) {
|
||||
survivor[0] = true;
|
||||
for (int i = 0; i < survivor.length; i++)
|
||||
if (survivor[i])
|
||||
if (survivor[i]) {
|
||||
culled.add(points.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return isCancelled() ? "" : "OK";
|
||||
|
@ -248,8 +234,9 @@ public abstract class AsynchronousResampler extends AsyncTask<String,Integer,Str
|
|||
int index = -1;
|
||||
for (int i = start + 1; i < end; i++) {
|
||||
|
||||
if (isCancelled())
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
double d = MapUtils.getOrthogonalDistance(
|
||||
points.get(i).getLatitude(), points.get(i).getLongitude(),
|
||||
|
|
|
@ -327,54 +327,24 @@ public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContex
|
|||
List<TrkSegment> segments = g.getPointsToDisplay();
|
||||
for (TrkSegment ts : segments) {
|
||||
|
||||
// TODO: Select/install renderables via UI or external process! (See comments below)
|
||||
if (ts.renders.size()==0 // only do once (CODE HERE NEEDS TO BE UI INSTEAD)
|
||||
&& !ts.points.isEmpty()) // hmmm. 0-point tracks happen, but.... how?
|
||||
{
|
||||
|
||||
// Each TrkSegment has one or more 'Renderables' - these handle drawing different things such
|
||||
// as the original track, rainbow altitude colouring, 1km markers, etc. They also handle the
|
||||
// asynchronous re-sampling of tracks for more efficient display. For example, the default
|
||||
// operation Renderable.RenderType.ORIGINAL does Ramer-Douglas-Peucer line optimisation for
|
||||
// zoom-level changes in track resolution. Change the '18' to '20' to see it more clearly.
|
||||
|
||||
// Renderables are processed in order that ALL have asynchronous resampling/culling capabililty.
|
||||
// In an ideal world, the renderers to be used are selected via UI or externally, and the
|
||||
// segment will already have them attached. For this first version, we add them the very
|
||||
// first time the TrkSegment gets drawn.
|
||||
|
||||
// NOTE: At the moment the 'ORIGINAL' renderer below is TOTALLY FUNCTIONALLY EQUIVALENT <<<IMPORTANT!!!
|
||||
// with the master branch/version before I added this capability, except that the track now resamples
|
||||
// based on zoom level and displays the resampled version when its available.
|
||||
|
||||
if (ts.renders.size()==0) { // only do once
|
||||
|
||||
// TODO: To see more clearly the line reduction in action, change the 18 to a higher number (say, 18.5 or 19)...
|
||||
|
||||
|
||||
ts.renders.add(new Renderable.StandardTrack(ts.points, 17)); // the base line (distance modifier)
|
||||
ts.renders.add(new Renderable.Altitude(ts.points, 50, 128));
|
||||
ts.renders.add(new Renderable.StandardTrack(ts.points, 17));
|
||||
|
||||
// TODO : enable these to see how the experimental conveyor, altitude, speed, waypoint renders work
|
||||
|
||||
// Note: the conveyor is EXAMPLE ONLY just to show an example of multiple renderables being used
|
||||
// - it is intended to show how support for arrows in route-based rendering can be supported by this
|
||||
// type of system. Please leave the code alone, and I will implement the arrows after this code is approved!
|
||||
|
||||
// You can, of course, comment out the following...
|
||||
|
||||
ts.renders.add(new Renderable.AltitudeColours(ts.points, 50, 128));
|
||||
ts.renders.add(new Renderable.Conveyor(ts.points, view, 20, 250));
|
||||
ts.renders.add(new Renderable.DistanceMarker(ts.points, 1000, 1));
|
||||
//ts.renders.add(new Renderable.SpeedColours(ts.points, 50, 128));
|
||||
|
||||
// [Note 1]: The altitude and speed renders (only if enabled, above) have a bug that can crash OsmAnd
|
||||
// - crashes on the emulator only!
|
||||
// - works fine on real hardware
|
||||
// - CAN YOU HELP ME FIND/UNDERSTAND IT???
|
||||
// [Note 2]: we only see valid altitude data if the GPX file has 'ele' tags
|
||||
// [Note 3]: we only see valid speed data if the GPX has 'time' tags
|
||||
//ts.renders.add(new Renderable.Conveyor(ts.points, view, 20, 250));
|
||||
ts.renders.add(new Renderable.DistanceMarker(ts.points, view, 1000));
|
||||
//ts.renders.add(new Renderable.Speed(ts.points, 50, 128));
|
||||
ts.renders.add(new Renderable.Arrows(ts.points,view,10,250));
|
||||
}
|
||||
|
||||
ts.recalculateRenderScales(view); // rework all renderers as required
|
||||
ts.recalculateRenderScales(view.getZoom());
|
||||
updatePaints(ts.getColor(cachedColor), g.isRoutePoints(), g.isShowCurrentTrack(), settings, tileBox);
|
||||
ts.drawRenderers(paint, canvas, tileBox); // any renderers now get to draw
|
||||
ts.drawRenderers(paint, canvas, tileBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -402,45 +372,6 @@ public class GPXLayer extends OsmandMapLayer implements ContextMenuLayer.IContex
|
|||
@Override
|
||||
public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
|
||||
}
|
||||
|
||||
/*
|
||||
ORIGINAL drawSegment below
|
||||
This is now moved to become one of the 'Renderable' types in Renderable.java
|
||||
|
||||
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) ;
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.List;
|
|||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
|
||||
import gnu.trove.list.array.TIntArrayList;
|
||||
|
||||
|
||||
|
@ -53,25 +54,47 @@ public class Renderable {
|
|||
protected List<GPXUtilities.WptPt> points = null; // Original list of points
|
||||
protected List<GPXUtilities.WptPt> culled = null; // Reduced/resampled list of points
|
||||
|
||||
double hash;
|
||||
protected QuadRect trackBounds = new QuadRect(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
|
||||
Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
|
||||
|
||||
|
||||
double hashZoom;
|
||||
double hashPoint;
|
||||
|
||||
boolean shadow = false; // TODO: fixup shadow support
|
||||
|
||||
AsynchronousResampler culler = null; // The currently active resampler
|
||||
|
||||
public RenderableSegment(List<GPXUtilities.WptPt> pt) {
|
||||
points = pt;
|
||||
hash = 0;
|
||||
hashPoint = points.hashCode();
|
||||
hashZoom = 0;
|
||||
culled = null;
|
||||
}
|
||||
|
||||
public void recalculateRenderScale(OsmandMapTileView view) {}
|
||||
public void recalculateRenderScale(double zoom) {}
|
||||
public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) {}
|
||||
|
||||
// When the asynchronous task has finished, it calls this function to set the 'culled' list
|
||||
public void setRDP(List<GPXUtilities.WptPt> cull) {
|
||||
|
||||
culled = cull;
|
||||
if (view != null)
|
||||
|
||||
// Find the segment's bounding box, to allow quick draw rejection later
|
||||
|
||||
trackBounds.left = trackBounds.bottom = Double.POSITIVE_INFINITY;
|
||||
trackBounds.right = trackBounds.top = Double.NEGATIVE_INFINITY;
|
||||
|
||||
for (GPXUtilities.WptPt pt : culled) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (view != null) {
|
||||
view.refreshMap(); // force a redraw
|
||||
}
|
||||
}
|
||||
|
||||
public List<GPXUtilities.WptPt> getPoints() {
|
||||
|
@ -95,7 +118,7 @@ public class Renderable {
|
|||
}
|
||||
|
||||
|
||||
// When there is a zoom change OR the list of points changes, then we want to trigger a
|
||||
// When there is a zoom change, then we want to trigger a
|
||||
// cull of the original point list (for example, Ramer-Douglas-Peucer algorithm or a
|
||||
// simple distance-based resampler. The cull operates asynchronously and results will be
|
||||
// returned into 'culled' via 'setRDP' algorithm (above).
|
||||
|
@ -106,24 +129,34 @@ public class Renderable {
|
|||
// 2. Individual derived classes (altitude, speed, etc) can override this routine to
|
||||
// ensure that the cull only ever happens once.
|
||||
|
||||
@Override public void recalculateRenderScale(OsmandMapTileView view) {
|
||||
@Override public void recalculateRenderScale(double zoom) {
|
||||
|
||||
// Here we create the 'shadow' resampled/culled points list, based on the asynchronous call.
|
||||
// The asynchronous callback will set the variable, and that is used for rendering
|
||||
// The asynchronous callback will set the variable, and that is preferentially used for rendering
|
||||
|
||||
double zoom = view.getZoom();
|
||||
double hashCode = points.hashCode() ;
|
||||
|
||||
if (points != null) {
|
||||
double hashCode = points.hashCode() + zoom;
|
||||
if (culled == null || hash != hashCode) {
|
||||
if (culler != null)
|
||||
culler.cancel(true); // stop any still-running cull
|
||||
hash = hashCode;
|
||||
double cullDistance = Math.pow(2.0,base-zoom);
|
||||
culler = new AsynchronousResampler.RamerDouglasPeucer(this, cullDistance);
|
||||
culled = null; // effectively use full-resolution until re-cull complete (see [Note 1] below)
|
||||
culler.execute("");
|
||||
if (hashPoint != hashCode) { // current track, changing?
|
||||
if (culler != null) {
|
||||
culler.cancel(true); // STOP culling a track with changing points
|
||||
culled = null; // and force use of original track
|
||||
}
|
||||
} else if (culler == null || hashZoom != zoom) {
|
||||
|
||||
hashZoom = zoom;
|
||||
|
||||
if (culler != null) {
|
||||
culler.cancel(true);
|
||||
}
|
||||
|
||||
double cullDistance = Math.pow(2.0, base - zoom);
|
||||
culler = new AsynchronousResampler.RamerDouglasPeucer(this, cullDistance);
|
||||
culled = null; // use full-resolution until re-cull complete (see [Note 1] below)
|
||||
culler.execute("");
|
||||
|
||||
// The trackBounds may be slightly inaccurate (unlikely, but...) so let's reset it
|
||||
//trackBounds.left = trackBounds.bottom = Double.POSITIVE_INFINITY;
|
||||
//trackBounds.right = trackBounds.bottom = Double.NEGATIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +165,10 @@ public class Renderable {
|
|||
|
||||
List<GPXUtilities.WptPt> pts = culled == null? points: culled; // [Note 1]: use culled points preferentially
|
||||
|
||||
final QuadRect latLonBounds = tileBox.getLatLonBounds();
|
||||
QuadRect latLonBounds = tileBox.getLatLonBounds();
|
||||
if (!QuadRect.trivialOverlap(latLonBounds, trackBounds)) {
|
||||
return; // Not visible
|
||||
}
|
||||
|
||||
int startIndex = -1;
|
||||
int endIndex = -1;
|
||||
|
@ -147,10 +183,7 @@ public class Renderable {
|
|||
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 (endIndex != i - 1 || startIndex == -1) {
|
||||
if (startIndex >= 0) {
|
||||
drawSegment(pts, p, canvas, tileBox, startIndex, endIndex);
|
||||
}
|
||||
|
@ -178,10 +211,9 @@ public class Renderable {
|
|||
ty.add((int)(tb.getPixYFromLatLon(p.lat, p.lon) + 0.5));
|
||||
}
|
||||
|
||||
//TODO: colour
|
||||
calculatePath(tb, tx, ty, path);
|
||||
|
||||
if (shadow) {
|
||||
if (shadow) { // needs work, but let's leave it like this for now
|
||||
float sw = paint.getStrokeWidth();
|
||||
int col = paint.getColor();
|
||||
paint.setColor(Color.BLACK);
|
||||
|
@ -217,7 +249,7 @@ public class Renderable {
|
|||
px, py, 0, w, h, 0);
|
||||
if (intersection != -1) {
|
||||
px = (int) (intersection >> 32);
|
||||
py = (int) (intersection & 0xffffffff);
|
||||
py = (int) (intersection & 0xffffffff); //TODO: Surely this is just intersection...!
|
||||
draw = true;
|
||||
}
|
||||
}
|
||||
|
@ -241,37 +273,34 @@ public class Renderable {
|
|||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
public static class AltitudeColours extends RenderableSegment {
|
||||
public static class Altitude extends RenderableSegment {
|
||||
|
||||
private Paint alphaPaint = null;
|
||||
private int alpha;
|
||||
protected float colorBandWidth; // width of speed/altitude colour band
|
||||
protected double segmentSize;
|
||||
|
||||
public AltitudeColours(List<GPXUtilities.WptPt> pt, double segmentSize, int alpha) {
|
||||
public Altitude(List<GPXUtilities.WptPt> pt, double segmentSize, int alpha) {
|
||||
super(pt);
|
||||
|
||||
this.segmentSize = segmentSize;
|
||||
this.alpha = alpha;
|
||||
alphaPaint = new Paint();
|
||||
alphaPaint.setStrokeCap(Paint.Cap.BUTT);
|
||||
colorBandWidth = 3.0f;
|
||||
alphaPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
colorBandWidth = 16f;
|
||||
}
|
||||
|
||||
public double getSegmentSize() {
|
||||
return segmentSize;
|
||||
}
|
||||
|
||||
@Override public void recalculateRenderScale(OsmandMapTileView view) {
|
||||
if (culler == null && culled == null) {
|
||||
culler = new AsynchronousResampler.ResampleAltitude(this, segmentSize);
|
||||
@Override public void recalculateRenderScale(double zoom) {
|
||||
if (culled == null && culler == null) {
|
||||
culler = new AsynchronousResampler.ResampleAltitude(this, segmentSize); // once only!
|
||||
culler.execute("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) {
|
||||
|
||||
if (culled != null && culled.size() > 0) {
|
||||
if (culled != null && !culled.isEmpty()
|
||||
&& QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) {
|
||||
|
||||
// Draws into a bitmap so that the lines can be drawn solid and the *ENTIRE* can be alpha-blended
|
||||
|
||||
|
@ -280,7 +309,7 @@ public class Renderable {
|
|||
canvas2.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
|
||||
alphaPaint.setAlpha(255);
|
||||
alphaPaint.setStrokeWidth(p.getStrokeWidth()*colorBandWidth);
|
||||
alphaPaint.setStrokeWidth(p.getStrokeWidth()*4.0f); // colorBandWidth
|
||||
|
||||
|
||||
float lastx = Float.NEGATIVE_INFINITY;
|
||||
|
@ -303,20 +332,19 @@ public class Renderable {
|
|||
canvas.drawBitmap(newBitmap, 0, 0, alphaPaint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
public static class SpeedColours extends AltitudeColours {
|
||||
public static class SpeedColours extends Altitude {
|
||||
|
||||
public SpeedColours(List<GPXUtilities.WptPt> pt, double segmentSize, int alpha) {
|
||||
super(pt, segmentSize, alpha);
|
||||
}
|
||||
|
||||
@Override public void recalculateRenderScale(OsmandMapTileView view) {
|
||||
if (culler == null && culled == null) {
|
||||
culler = new AsynchronousResampler.ResampleSpeed(this, segmentSize);
|
||||
@Override public void recalculateRenderScale(double zoom) {
|
||||
if (culled == null && culler == null) {
|
||||
culler = new AsynchronousResampler.ResampleSpeed(this, segmentSize); // once only!
|
||||
culler.execute("");
|
||||
}
|
||||
}
|
||||
|
@ -335,14 +363,14 @@ public class Renderable {
|
|||
Renderable.startScreenRefresh(view, refreshRate);
|
||||
}
|
||||
|
||||
@Override public void recalculateRenderScale(OsmandMapTileView view) {
|
||||
if (culled == null && culler == null) { // i.e., do NOT resample when scaling - only allow a one-off generation
|
||||
culler = new AsynchronousResampler.GenericResampler(this, segmentSize);
|
||||
@Override public void recalculateRenderScale(double zoom) {
|
||||
if (culled == null && culler == null) {
|
||||
culler = new AsynchronousResampler.GenericResampler(this, segmentSize); // once only!
|
||||
culler.execute("");
|
||||
}
|
||||
}
|
||||
|
||||
public int getComplementaryColor(int colorToInvert) {
|
||||
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;
|
||||
|
@ -355,104 +383,110 @@ public class Renderable {
|
|||
// effects of constant segment-size can be used for animation effects. I've put an arrowhead
|
||||
// in just to show what can be done. Very hacky, it's just a "hey look at this".
|
||||
|
||||
if (culled == null)
|
||||
return;
|
||||
if (culled != null && !culled.isEmpty()
|
||||
/* && !QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)*/ ) {
|
||||
|
||||
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
|
||||
int pCol = p.getColor();
|
||||
float pSw = p.getStrokeWidth();
|
||||
int pCol = p.getColor();
|
||||
float pSw = p.getStrokeWidth();
|
||||
|
||||
//p.setStrokeWidth(pSw * 2f); // use a thicker line
|
||||
p.setColor(getComplementaryColor(p.getColor())); // and a complementary colour
|
||||
//p.setStrokeWidth(pSw * 2f); // use a thicker line
|
||||
p.setColor(getComplementaryColor(p.getColor())); // and a complementary colour
|
||||
|
||||
float lastx = Float.NEGATIVE_INFINITY;
|
||||
float lasty = Float.NEGATIVE_INFINITY;
|
||||
Path path = new Path();
|
||||
float lastx = Float.NEGATIVE_INFINITY;
|
||||
float lasty = Float.NEGATIVE_INFINITY;
|
||||
Path path = new Path();
|
||||
|
||||
int h = tileBox.getPixHeight();
|
||||
int w = tileBox.getPixWidth();
|
||||
boolean broken = true;
|
||||
int intp = conveyor; // the segment cycler
|
||||
for (GPXUtilities.WptPt pt : culled) {
|
||||
intp--; // increment to go the other way!
|
||||
int h = tileBox.getPixHeight();
|
||||
int w = tileBox.getPixWidth();
|
||||
boolean broken = true;
|
||||
int intp = conveyor; // the segment cycler
|
||||
for (GPXUtilities.WptPt pt : culled) {
|
||||
intp--; // increment to go the other way!
|
||||
|
||||
if ((intp & 7) < 3) {
|
||||
if ((intp & 7) < 3) {
|
||||
|
||||
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
|
||||
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
|
||||
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
|
||||
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
|
||||
|
||||
if ((isIn(x, y, w, h) || isIn(lastx, lasty, w, h))) {
|
||||
if (broken) {
|
||||
path.moveTo(x, y);
|
||||
broken = false;
|
||||
} else
|
||||
path.lineTo(x, y);
|
||||
lastx = x;
|
||||
lasty = y;
|
||||
} else
|
||||
if ((isIn(x, y, w, h) || isIn(lastx, lasty, w, h))) {
|
||||
if (broken) {
|
||||
path.moveTo(x, y);
|
||||
broken = false;
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
lastx = x;
|
||||
lasty = y;
|
||||
} else {
|
||||
broken = true;
|
||||
}
|
||||
} else {
|
||||
broken = true;
|
||||
} else
|
||||
broken = true;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.drawPath(path, p);
|
||||
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
|
||||
p.setStrokeWidth(pSw);
|
||||
p.setColor(pCol);
|
||||
}
|
||||
|
||||
canvas.drawPath(path, p);
|
||||
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
|
||||
p.setStrokeWidth(pSw);
|
||||
p.setColor(pCol);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
public static class DistanceMarker extends RenderableSegment {
|
||||
// EXPERIMENTAL!
|
||||
|
||||
private float dotScale;
|
||||
private double segmentSize;
|
||||
private OsmandMapTileView view;
|
||||
|
||||
public DistanceMarker(List<GPXUtilities.WptPt> pt, double segmentSize, float dotScale) {
|
||||
public DistanceMarker(List<GPXUtilities.WptPt> pt, OsmandMapTileView view, double segmentSize) {
|
||||
super(pt);
|
||||
this.dotScale = dotScale;
|
||||
this.view = view;
|
||||
this.dotScale = view.getScaleCoefficient();
|
||||
this.segmentSize = segmentSize;
|
||||
}
|
||||
|
||||
@Override public void recalculateRenderScale(OsmandMapTileView view) {
|
||||
@Override public void recalculateRenderScale(double zoom) {
|
||||
if (culled == null && culler == null) {
|
||||
culler = new AsynchronousResampler.GenericResampler(this, segmentSize);
|
||||
assert (culler != null);
|
||||
if (culler != null)
|
||||
culler.execute("");
|
||||
culler.execute("");
|
||||
}
|
||||
}
|
||||
|
||||
private String getLabel(double value) {
|
||||
String lab;
|
||||
lab = String.format("%.2f km",value/1000.);
|
||||
|
||||
int dig2 = (int)(value / 10);
|
||||
if ((dig2%10)==0)
|
||||
lab = String.format("%d km",value/100);
|
||||
else
|
||||
lab = String.format("%.2f km", value/1000.);
|
||||
return lab;
|
||||
}
|
||||
|
||||
@Override public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) {
|
||||
|
||||
assert p != null;
|
||||
assert canvas != null;
|
||||
assert tileBox != null;
|
||||
|
||||
try {
|
||||
|
||||
if (culled == null)
|
||||
return;
|
||||
if (culled != null && !culled.isEmpty()
|
||||
&& !QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)) {
|
||||
|
||||
Paint px = new Paint();
|
||||
assert (px != null);
|
||||
|
||||
px.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
|
||||
// rubbish... trying to understand screen scaling/density...
|
||||
|
||||
float viewScale = view.getScaleCoefficient()/4.0f; // now "1" for emulator sizing
|
||||
float density = view.getDensity();
|
||||
float ds = 160 / viewScale; // "10pt"
|
||||
float sw = p.getStrokeWidth();
|
||||
float ds = sw * dotScale;
|
||||
|
||||
|
||||
int w = tileBox.getPixWidth();
|
||||
int h = tileBox.getPixHeight();
|
||||
|
@ -465,28 +499,21 @@ public class Renderable {
|
|||
if (isIn(x, y, w, h)) {
|
||||
|
||||
px.setColor(0xFF000000);
|
||||
px.setStrokeWidth(ds + 2);
|
||||
px.setStrokeWidth(sw + 4);
|
||||
canvas.drawPoint(x, y, px);
|
||||
px.setStrokeWidth(ds);
|
||||
px.setStrokeWidth(sw+2);
|
||||
px.setColor(0xFFFFFFFF);
|
||||
canvas.drawPoint(x, y, px);
|
||||
|
||||
//TODO: I do not know how to correctly handle screen density!
|
||||
//TODO: modify the text size based on density calculations!!
|
||||
|
||||
if (sw > 6) {
|
||||
if (view.getZoom()>11) {
|
||||
px.setColor(Color.BLACK);
|
||||
px.setStrokeWidth(1);
|
||||
px.setTextSize(sw*2f); //<<< TODO fix
|
||||
px.setTextSize(ds);
|
||||
canvas.drawText(getLabel(pt.getDistance()), x + ds / 2, y + ds / 2, px);
|
||||
}
|
||||
}
|
||||
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String exception = e.getMessage();
|
||||
Throwable cause = e.getCause();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -494,8 +521,10 @@ public class Renderable {
|
|||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
public static class Arrows extends RenderableSegment {
|
||||
// EXPERIMENTAL!
|
||||
|
||||
private double segmentSize;
|
||||
private double zoom;
|
||||
|
||||
public Arrows(List<GPXUtilities.WptPt> pt, OsmandMapTileView view, double segmentSize, long refreshRate) {
|
||||
super(pt);
|
||||
|
@ -504,9 +533,10 @@ public class Renderable {
|
|||
Renderable.startScreenRefresh(view, refreshRate);
|
||||
}
|
||||
|
||||
@Override public void recalculateRenderScale(OsmandMapTileView view) {
|
||||
if (culled == null && culler == null) { // i.e., do NOT resample when scaling - only allow a one-off generation
|
||||
culler = new AsynchronousResampler.GenericResampler(this, segmentSize);
|
||||
@Override public void recalculateRenderScale(double zoom) {
|
||||
this.zoom = zoom;
|
||||
if (culled == null && culler == null) {
|
||||
culler = new AsynchronousResampler.GenericResampler(this, segmentSize); // once
|
||||
culler.execute("");
|
||||
}
|
||||
}
|
||||
|
@ -520,57 +550,78 @@ public class Renderable {
|
|||
|
||||
@Override public void drawSingleSegment(Paint p, Canvas canvas, RotatedTileBox tileBox) {
|
||||
|
||||
// This is a simple/experimental track subsegment 'conveyor' animator just to show how
|
||||
// effects of constant segment-size can be used for animation effects. I've put an arrowhead
|
||||
// in just to show what can be done. Very hacky, it's just a "hey look at this".
|
||||
if (culled != null && !culled.isEmpty()
|
||||
/*&& !QuadRect.trivialOverlap(tileBox.getLatLonBounds(), trackBounds)*/) {
|
||||
|
||||
if (culled == null)
|
||||
return;
|
||||
// This is all very hacky and experimental code. Just showing how to do an animating segmented
|
||||
// line to draw arrows in the direction of movement.
|
||||
|
||||
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
|
||||
int pCol = p.getColor();
|
||||
float pSw = p.getStrokeWidth();
|
||||
float sizer = (float) Math.pow(2.0,zoom-18) * 512;
|
||||
int pCol = p.getColor();
|
||||
|
||||
//p.setStrokeWidth(pSw * 2f); // use a thicker line
|
||||
p.setColor(getComplementaryColor(p.getColor())); // and a complementary colour
|
||||
p.setColor(getComplementaryColor(p.getColor())); // and a complementary colour
|
||||
|
||||
float lastx = Float.NEGATIVE_INFINITY;
|
||||
float lasty = Float.NEGATIVE_INFINITY;
|
||||
Path path = new Path();
|
||||
float lastx = Float.NEGATIVE_INFINITY;
|
||||
float lasty = Float.NEGATIVE_INFINITY;
|
||||
Path path = new Path();
|
||||
|
||||
int h = tileBox.getPixHeight();
|
||||
int w = tileBox.getPixWidth();
|
||||
boolean broken = true;
|
||||
int intp = conveyor; // the segment cycler
|
||||
for (GPXUtilities.WptPt pt : culled) {
|
||||
intp--; // increment to go the other way!
|
||||
int h = tileBox.getPixHeight();
|
||||
int w = tileBox.getPixWidth();
|
||||
boolean broken = true;
|
||||
int intp = conveyor; // the segment cycler
|
||||
for (GPXUtilities.WptPt pt : culled) {
|
||||
intp--; // increment to go the other way!
|
||||
|
||||
if ((intp & 7) < 3) {
|
||||
if ((intp & 15) < 8) {
|
||||
|
||||
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
|
||||
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
|
||||
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
|
||||
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
|
||||
|
||||
if ((isIn(x, y, w, h) || isIn(lastx, lasty, w, h))) {
|
||||
if (broken) {
|
||||
path.moveTo(x, y);
|
||||
broken = false;
|
||||
} else
|
||||
|
||||
if ((intp&15) == 0) {
|
||||
// arrowhead - trial and error trig till it looked OK :)
|
||||
double angle = Math.atan2(lasty-y,lastx-x);
|
||||
float newx1 = x + (float)Math.sin(angle-0.4+Math.PI/2)*sizer;
|
||||
float newy1 = y - (float)Math.cos(angle-0.4+Math.PI/2)*sizer;
|
||||
float newx2 = x + (float)Math.sin(angle+0.4+Math.PI/2)*sizer;
|
||||
float newy2 = y - (float)Math.cos(angle+0.4+Math.PI/2)*sizer;
|
||||
|
||||
if (broken) {
|
||||
path.moveTo(x, y);
|
||||
}
|
||||
|
||||
path.lineTo(x,y);
|
||||
path.moveTo(newx1, newy1);
|
||||
path.lineTo(x, y);
|
||||
lastx = x;
|
||||
lasty = y;
|
||||
path.lineTo(newx2, newy2);
|
||||
path.moveTo(x,y);
|
||||
broken = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ((isIn(x, y, w, h) || isIn(lastx, lasty, w, h))) {
|
||||
if (broken) {
|
||||
path.moveTo(x, y);
|
||||
broken = false;
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
lastx = x;
|
||||
lasty = y;
|
||||
} else
|
||||
broken = true;
|
||||
} else
|
||||
broken = true;
|
||||
} else
|
||||
broken = true;
|
||||
|
||||
}
|
||||
|
||||
canvas.drawPath(path, p);
|
||||
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
p.setColor(pCol);
|
||||
}
|
||||
|
||||
canvas.drawPath(path, p);
|
||||
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
|
||||
|
||||
p.setStrokeWidth(pSw);
|
||||
p.setColor(pCol);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue