android中的彩色折线映射api v2

android中的彩色折线映射api v2,第1张

概述我想在androidmapsapi版本2中绘制折线.我希望它有很多颜色,最好是渐变色.在我看来,折线只允许单色.我怎样才能做到这一点?我已经有api-v1叠加绘制我喜欢的东西,所以我可以重用一些代码publicclassRouteOverlayGoogleextendsOverlay{publicvoiddraw(Canvascanvas,

我想在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所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1114503.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-29
下一篇 2022-05-29

发表评论

登录后才能评论

评论列表(0条)

保存