我想在android maps API版本2中绘制折线.我希望它有很多颜色,最好是渐变色.在我看来,折线只允许单色.
我怎样才能做到这一点?我已经有API-v1叠加绘制我喜欢的东西,所以我可以重用一些代码
public class RouteOverlayGoogle extends Overlay { public voID draw(Canvas canvas, MapVIEw mapVIEw, boolean shadow) { //(...) draws line with color representing speed }
解决方法:
我知道已经有很长一段时间了,但是仍然没有渐变多段线(截至写作,〜可能2015年)并且绘制多条折线确实没有切割它(锯齿状边缘,相当多的滞后时间)处理几百个点,只是在视觉上没有吸引力).
当我必须实现渐变折线时,我最终要做的是实现一个TileOverlay,它将折线渲染到画布然后光栅化它(请参阅我要写的特定代码的要点https://gist.github.com/Dagothig/5f9cf0a4a7a42901a7b2).
该实现不会尝试进行任何类型的视口剔除,因为我最终不需要它来达到我想要的性能(我不确定数字,但是每个瓷砖的时间不到一秒,多个瓷砖将是同时渲染).
渲染渐变折线可能非常棘手,但是因为你正在处理不同的视口(位置和大小):更重要的是,我在高缩放级别(20)处遇到浮点精度限制的几个问题.最后我没有使用缩放和从画布翻译功能,因为我会得到奇怪的腐败问题.
如果你使用类似的数据结构(纬度,经度和时间戳)需要多个段来正确渲染渐变(我最终一次使用3个点),需要注意的其他事项.
为了后人的缘故,我还要把这些代码留在这里:
(如果您想知道com.Google.maps.androID.projection.SphericalMercatorProjection来自哪里,则使用https://github.com/googlemaps/android-maps-utils完成投影)
import androID.content.Context;import androID.graphics.Bitmap;import androID.graphics.Canvas;import androID.graphics.color;import androID.graphics.linearGradIEnt;import androID.graphics.Matrix;import androID.graphics.Paint;import androID.graphics.Shader;import com.Google.androID.gms.maps.model.LatLng;import com.Google.androID.gms.maps.model.Tile;import com.Google.androID.gms.maps.model.TileProvIDer;import com.Google.maps.androID.SphericalUtil;import com.Google.maps.androID.geometry.Point;import com.Google.maps.androID.projection.SphericalMercatorProjection;import java.io.ByteArrayOutputStream;import java.util.List;/** * Tile overlay used to display a colored polyline as a replacement for the non-existence of gradIEnt * polylines for Google maps */public class coloredpolylineTileOverlay<T extends coloredpolylineTileOverlay.PointHolder> implements TileProvIDer { public static final double LOW_SPEED_CLAMP_KMpH = 0; public static final double LOW_SPEED_CLAMP_MpS = 0; // Todo: calculate speed as highest speed of pointsCollection public static final double HIGH_SPEED_CLAMP_KMpH = 50; public static final double HIGH_SPEED_CLAMP_MpS = HIGH_SPEED_CLAMP_KMpH * 1000 / (60 * 60); public static final int BASE_TILE_SIZE = 256; public static int[] getSpeedcolors(Context context) { return new int[] { context.getResources().getcolor(R.color.polyline_low_speed), context.getResources().getcolor(R.color.polyline_med_speed), context.getResources().getcolor(R.color.polyline_high_speed) }; } public static float getSpeedProportion(double metersPerSecond) { return (float)(Math.max(Math.min(metersPerSecond, HIGH_SPEED_CLAMP_MpS), LOW_SPEED_CLAMP_MpS) / HIGH_SPEED_CLAMP_MpS); } public static int interpolatecolor(int[] colors, float proportion) { int rTotal = 0, gTotal = 0, bTotal = 0; // We correct the ratio to colors.length - 1 so that // for i == colors.length - 1 and p == 1, then the final ratio is 1 (see below) float p = proportion * (colors.length - 1); for (int i = 0; i < colors.length; i++) { // The ratio mostly resIDes on the 1 - Math.abs(p - i) calculation : // Since for p == i, then the ratio is 1 and for p == i + 1 or p == i -1, then the ratio is 0 // This calculation works BECAUSE p lIEs within [0, length - 1] and i lIEs within [0, length - 1] as well float iRatio = Math.max(1 - Math.abs(p - i), 0.0f); rTotal += (int)(color.red(colors[i]) * iRatio); gTotal += (int)(color.green(colors[i]) * iRatio); bTotal += (int)(color.blue(colors[i]) * iRatio); } return color.rgb(rTotal, gTotal, bTotal); } protected final Context context; protected final PointCollection<T> pointsCollection; protected final int[] speedcolors; protected final float density; protected final int tileDimension; protected final SphericalMercatorProjection projection; // Caching calculation-related stuff protected LatLng[] trailLatLngs; protected Point[] projectedPts; protected Point[] projectedPtMIDs; protected double[] speeds; public coloredpolylineTileOverlay(Context context, PointCollection pointsCollection) { super(); this.context = context; this.pointsCollection = pointsCollection; speedcolors = getSpeedcolors(context); density = context.getResources().getdisplayMetrics().density; tileDimension = (int)(BASE_TILE_SIZE * density); projection = new SphericalMercatorProjection(BASE_TILE_SIZE); calculatePointsAndSpeeds(); } public voID calculatePointsAndSpeeds() { trailLatLngs = new LatLng[pointsCollection.getPoints().size()]; projectedPts = new Point[pointsCollection.getPoints().size()]; projectedPtMIDs = new Point[Math.max(pointsCollection.getPoints().size() - 1, 0)]; speeds = new double[Math.max(pointsCollection.getPoints().size() - 1, 0)]; List<T> points = pointsCollection.getPoints(); for (int i = 0; i < points.size(); i++) { T point = points.get(i); LatLng latLng = point.getLatLng(); trailLatLngs[i] = latLng; projectedPts[i] = projection.topoint(latLng); // MIDs if (i > 0) { LatLng prevIoUsLatLng = points.get(i - 1).getLatLng(); LatLng latLngMID = SphericalUtil.interpolate(prevIoUsLatLng, latLng, 0.5); projectedPtMIDs[i - 1] = projection.topoint(latLngMID); T prevIoUsPoint = points.get(i - 1); double speed = SphericalUtil.computedistanceBetween(latLng, prevIoUsLatLng) / ((point.getTime() - prevIoUsPoint.getTime()) / 1000.0); speeds[i - 1] = speed; } } } @OverrIDe public Tile getTile(int x, int y, int zoom) { // Because getTile can be called asynchronously by multiple threads, none of the info we keep in the class will be modifIEd // (getTile is essentially sIDe-effect-less) : // Instead, we create the bitmap, the canvas and the paints specifically for the call to getTile Bitmap bitmap = Bitmap.createBitmap(tileDimension, tileDimension, Bitmap.Config.ARGB_8888); // normally, instead of the later calls for drawing being offset, we would offset them using scale() and translate() right here // However, there seems to be funky issues related to float imprecisions that happen at large scales when using this method, so instead // The points are offset properly when drawing Canvas canvas = new Canvas(bitmap); Matrix shaderMat = new Matrix(); Paint gradIEntPaint = new Paint(); gradIEntPaint.setStyle(Paint.Style.stroke); gradIEntPaint.setstrokeWIDth(3f * density); gradIEntPaint.setstrokeCap(Paint.Cap.BUTT); gradIEntPaint.setstrokeJoin(Paint.Join.ROUND); gradIEntPaint.setFlags(Paint.ANTI_AliAS_FLAG); gradIEntPaint.setShader(new linearGradIEnt(0, 0, 1, 0, speedcolors, null, Shader.TileMode.CLAMP)); gradIEntPaint.getShader().setLocalMatrix(shaderMat); Paint colorPaint = new Paint(); colorPaint.setStyle(Paint.Style.stroke); colorPaint.setstrokeWIDth(3f * density); colorPaint.setstrokeCap(Paint.Cap.BUTT); colorPaint.setstrokeJoin(Paint.Join.ROUND); colorPaint.setFlags(Paint.ANTI_AliAS_FLAG); // See https://developers.Google.com/maps/documentation/androID/vIEws#zoom for handy info regarding what zoom is float scale = (float)(Math.pow(2, zoom) * density); renderTrail(canvas, shaderMat, gradIEntPaint, colorPaint, scale, x, y); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); return new Tile(tileDimension, tileDimension, baos.toByteArray()); } public voID renderTrail(Canvas canvas, Matrix shaderMat, Paint gradIEntPaint, Paint colorPaint, float scale, int x, int y) { List<T> points = pointsCollection.getPoints(); double speed1, speed2; MutPoint pt1 = new MutPoint(), pt2 = new MutPoint(), pt3 = new MutPoint(), pt1mID2 = new MutPoint(), pt2mID3 = new MutPoint(); // Guard statement: if the trail is only 1 point, just render the point by itself as a speed of 0 if (points.size() == 1) { pt1.set(projectedPts[0], scale, x, y, tileDimension); speed1 = 0; float speedProp = getSpeedProportion(speed1); colorPaint.setStyle(Paint.Style.FILL); colorPaint.setcolor(interpolatecolor(speedcolors, speedProp)); canvas.drawCircle((float) pt1.x, (float) pt1.y, colorPaint.getstrokeWIDth() / 2f, colorPaint); colorPaint.setStyle(Paint.Style.stroke); return; } // Guard statement: if the trail is exactly 2 points long, just render a line from A to B at d(A, B) / t speed if (points.size() == 2) { pt1.set(projectedPts[0], scale, x, y, tileDimension); pt2.set(projectedPts[1], scale, x, y, tileDimension); speed1 = speeds[0]; float speedProp = getSpeedProportion(speed1); drawline(canvas, colorPaint, pt1, pt2, speedProp); return; } // Because we want to be displaying speeds as color ratios, we need multiple points to do it properly: // Since we use calculate the speed using the distance and the time, we need at least 2 points to calculate the distance; // this means we kNow the speed for a segment, not a point. // Furthermore, since we want to be easing the color changes between every segment, we have to use 3 points to do the easing; // every line is split into two, and we ease over the corners // This also means the first and last corners need to be extended to include the first and last points respectively // Finally (you can see about that in getTile()) we need to offset the point projections based on the scale and x, y because // weird display behavIoUr occurs for (int i = 2; i < points.size(); i++) { pt1.set(projectedPts[i - 2], scale, x, y, tileDimension); pt2.set(projectedPts[i - 1], scale, x, y, tileDimension); pt3.set(projectedPts[i], scale, x, y, tileDimension); // Because we want to split the lines in two to ease over the corners, we need the mIDdle points pt1mID2.set(projectedPtMIDs[i - 2], scale, x, y, tileDimension); pt2mID3.set(projectedPtMIDs[i - 1], scale, x, y, tileDimension); // The speed is calculated in meters per second (same format as the speed clamps); because getTime() is in millis, we need to correct for that speed1 = speeds[i - 2]; speed2 = speeds[i - 1]; float speed1Prop = getSpeedProportion(speed1); float speed1to2Prop = getSpeedProportion((speed1 + speed2) / 2); float speed2Prop = getSpeedProportion(speed2); // Circle for the corner (removes the weird empty corners that occur otherwise) colorPaint.setStyle(Paint.Style.FILL); colorPaint.setcolor(interpolatecolor(speedcolors, speed1to2Prop)); canvas.drawCircle((float)pt2.x, (float)pt2.y, colorPaint.getstrokeWIDth() / 2f, colorPaint); colorPaint.setStyle(Paint.Style.stroke); // Corner // Note that since for the very first point and the very last point we don't split it in two, we used them instead. drawline(canvas, shaderMat, gradIEntPaint, colorPaint, i - 2 == 0 ? pt1 : pt1mID2, pt2, speed1Prop, speed1to2Prop); drawline(canvas, shaderMat, gradIEntPaint, colorPaint, pt2, i == points.size() - 1 ? pt3 : pt2mID3, speed1to2Prop, speed2Prop); } } /** * Note: it is assumed the shader is 0, 0, 1, 0 (horizontal) so that it lines up with the rotation * (rotations are usually setup so that the angle 0 points right) */ public voID drawline(Canvas canvas, Matrix shaderMat, Paint gradIEntPaint, Paint colorPaint, MutPoint pt1, MutPoint pt2, float ratio1, float ratio2) { // Degenerate case: both ratios are the same; we just handle it using the colorPaint (handling it using the shader is just messy and ineffective) if (ratio1 == ratio2) { drawline(canvas, colorPaint, pt1, pt2, ratio1); return; } shaderMat.reset(); // PS: don't ask me why this specfic orders for calls works but other orders will mess up // Since every call is pre, this is essentially ordered as (or my understanding is that it is): // ratio translate -> ratio scale -> scale to pt length -> translate to pt start -> rotate // (my initial intuition was to use only post calls and to order as above, but it resulted in odd corruptions) // Setup based on points: // We translate the shader so that it is based on the first point, rotated towards the second and since the length of the // gradIEnt is 1, then scaling to the length of the distance between the points makes it exactly as long as needed shaderMat.preRotate((float) Math.todegrees(Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x)), (float)pt1.x, (float)pt1.y); shaderMat.preTranslate((float)pt1.x, (float)pt1.y); float scale = (float)Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); shaderMat.preScale(scale, scale); // Setup based on ratio // By basing the shader to the first ratio, we ensure that the start of the gradIEnt corresponds to it // The inverse scaling of the shader means that it takes the full length of the call to go to the second ratio // For instance; if d(ratio1, ratio2) is 0.5, then the shader needs to be twice as long so that an entire call (1) // Results in only half of the gradIEnt being used shaderMat.preScale(1f / (ratio2 - ratio1), 1f / (ratio2 - ratio1)); shaderMat.preTranslate(-ratio1, 0); gradIEntPaint.getShader().setLocalMatrix(shaderMat); canvas.drawline( (float)pt1.x, (float)pt1.y, (float)pt2.x, (float)pt2.y, gradIEntPaint ); } public voID drawline(Canvas canvas, Paint colorPaint, MutPoint pt1, MutPoint pt2, float ratio) { colorPaint.setcolor(interpolatecolor(speedcolors, ratio)); canvas.drawline( (float)pt1.x, (float)pt1.y, (float)pt2.x, (float)pt2.y, colorPaint ); } public interface PointCollection<T extends PointHolder> { List<T> getPoints(); } public interface PointHolder { LatLng getLatLng(); long getTime(); } public static class MutPoint { public double x, y; public MutPoint set(Point point, float scale, int x, int y, int tileDimension) { this.x = point.x * scale - x * tileDimension; this.y = point.y * scale - y * tileDimension; return this; } }}
请注意,此实现假定两个相对较大的事情:
>折线已经完成
>只有一条折线.
我认为处理(1)不会很困难.但是,如果您打算以这种方式绘制多条折线,则可能需要查看一些方法来增强性能(保持折线的边界框能够轻松地丢弃那些不适合视口的折线).
关于使用TileOverlay需要记住的另一件事是,它是在完成移动后渲染的,而不是在渲染过程中渲染的.因此,您可能需要使用其下方的实际单色折线来备份叠加层,以使其具有一定的连续性.
PS:这是我第一次尝试回答问题,所以如果有什么我应该修复或做的不同请告诉我.
总结以上是内存溢出为你收集整理的android中的彩色折线映射api v2全部内容,希望文章能够帮你解决android中的彩色折线映射api v2所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)