TileProvider图形在更高的缩放级别上会出现偏差

时间:2014-08-11 20:27:19

标签: android graphics google-maps-android-api-2 tile

我目前正在玩Android Maps API v2中的TileProver并且遇到了以下问题:我手动绘制到Bitmap中的图形在更高的缩放级别上显着偏斜:

graphics is skewed

让我解释一下我在这做什么。我有多个LatLng点,我为地图上的每个点画一个圆圈,这样当你放大时 - 点停留在同一个地理位置。正如您在屏幕截图中看到的那样,圆圈在较低的缩放级别上看起来很好,但是当您开始放大时 - 圆圈会变得偏斜..

它是如何实现的:

package trickyandroid.com.locationtracking;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.Log;

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.geometry.Point;
import com.google.maps.android.projection.SphericalMercatorProjection;

import java.io.ByteArrayOutputStream;

/**
 * Created by paveld on 8/8/14.
 */
public class CustomTileProvider implements TileProvider {

    private final int TILE_SIZE = 256;

    private int density = 1;
    private int tileSizeScaled;
    private Paint circlePaint;
    private SphericalMercatorProjection projection;
    private Point[] points;

    public CustomTileProvider(Context context) {
        density = 3; //hardcoded for now, but should be driven by DisplayMetrics.density
        tileSizeScaled = TILE_SIZE * density;

        projection = new SphericalMercatorProjection(TILE_SIZE);

        points = generatePoints();

        circlePaint = new Paint();
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(0xFF000000);
        circlePaint.setStyle(Paint.Style.FILL);
    }

    private Point[] generatePoints() {
        Point[] points = new Point[6];
        points[0] = projection.toPoint(new LatLng(47.603861, -122.333393));
        points[1] = projection.toPoint(new LatLng(47.600389, -122.326741));
        points[2] = projection.toPoint(new LatLng(47.598942, -122.318973));
        points[3] = projection.toPoint(new LatLng(47.599000, -122.311549));
        points[4] = projection.toPoint(new LatLng(47.601373, -122.301721));
        points[5] = projection.toPoint(new LatLng(47.609764, -122.311850));

        return points;
    }

    @Override
    public Tile getTile(int x, int y, int zoom) {
        Bitmap bitmap = Bitmap.createBitmap(tileSizeScaled, tileSizeScaled, Bitmap.Config.ARGB_8888);
        float scale = (float) (Math.pow(2, zoom) * density);
        Matrix m = new Matrix();
        m.setScale(scale, scale);
        m.postTranslate(-x * tileSizeScaled, -y * tileSizeScaled);

        Canvas c = new Canvas(bitmap);
        c.setMatrix(m);

        for (Point p : points) {
            c.drawCircle((float) p.x, (float) p.y, 20 / scale, circlePaint);
        }

        return bitmapToTile(bitmap);
    }

    private Tile bitmapToTile(Bitmap bmp) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
        byte[] bitmapdata = stream.toByteArray();
        return new Tile(tileSizeScaled, tileSizeScaled, bitmapdata);
    }
}

逻辑告诉我,这种情况正在发生,因为我只将LatLng转换为1个图块(256x256,即缩放级别0)的屏幕位置,然后为了将此屏幕点转换为其他缩放级别,我需要缩放我的位图并将其转换为适当的位置。同时,由于位图是缩放的,我需要补偿圆半径,所以我将半径除以比例因子。所以在缩放级别19,我的比例因子已经是1572864,这是巨大的。这就像通过巨大的放大镜看这个圆圈。这就是我产生这种效果的原因。

所以我认为解决方案是避免位图缩放并仅缩放/平移屏幕坐标。在这种情况下,我的圆半径将始终相同,并且不会缩小尺寸。

不幸的是,矩阵数学不是我最强的技能,所以我的问题是 - 我如何缩放/平移任意缩放级别的点集,其中有一组点数是为缩放级别而计算的?'?

执行此操作的最简单方法是为每个缩放级别设置不同的Projection实例,但由于GeoPoint - > ScreenPoint转换是非常昂贵的操作,我会将这种方法作为备份并使用一些简单的数学来翻译已经存在的屏幕点。

注意 请注意,我需要专门定制TileProvider,因为在应用程序中,我将绘制比圆圈更复杂的瓷砖。这么简单的Marker课程对我不起作用

更新 即使我弄清楚如何翻译单个点并避免位图缩放:

c.drawCircle((float) p.x * scale - (x * tileSizeScaled), (float) p.y * scale - (y * tileSizeScaled), 20, circlePaint);

我仍然不知道如何使用Path个对象执行此操作。我不能像使用单个点那样翻译/缩放路径,所以我仍然必须缩放我的位图,这会再次导致绘制瑕疵(笔划宽度在更高的缩放级别上倾斜):

enter image description here

这是一段代码:

package trickyandroid.com.locationtracking;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;

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.geometry.Point;
import com.google.maps.android.projection.SphericalMercatorProjection;

import java.io.ByteArrayOutputStream;

/**
 * Created by paveld on 8/8/14.
 */
public class CustomTileProvider implements TileProvider {

    private final int TILE_SIZE = 256;

    private int density = 1;
    private int tileSizeScaled;
    private SphericalMercatorProjection projection;
    private Point[] points;
    private Path path;
    private Paint pathPaint;

    public CustomTileProvider(Context context) {
        density = 3; //hardcoded for now, but should be driven by DisplayMetrics.density
        tileSizeScaled = TILE_SIZE * density;

        projection = new SphericalMercatorProjection(TILE_SIZE);

        points = generatePoints();
        path = generatePath(points);

        pathPaint = new Paint();
        pathPaint.setAntiAlias(true);
        pathPaint.setColor(0xFF000000);
        pathPaint.setStyle(Paint.Style.STROKE);
        pathPaint.setStrokeCap(Paint.Cap.ROUND);
        pathPaint.setStrokeJoin(Paint.Join.ROUND);
    }

    private Path generatePath(Point[] points) {
        Path path = new Path();
        path.moveTo((float) points[0].x, (float) points[0].y);
        for (int i = 1; i < points.length; i++) {
            path.lineTo((float) points[i].x, (float) points[i].y);
        }
        return path;
    }

    private Point[] generatePoints() {
        Point[] points = new Point[10];
        points[0] = projection.toPoint(new LatLng(47.603861, -122.333393));
        points[1] = projection.toPoint(new LatLng(47.600389, -122.326741));
        points[2] = projection.toPoint(new LatLng(47.598942, -122.318973));
        points[3] = projection.toPoint(new LatLng(47.599000, -122.311549));
        points[4] = projection.toPoint(new LatLng(47.601373, -122.301721));
        points[5] = projection.toPoint(new LatLng(47.609764, -122.311850));
        points[6] = projection.toPoint(new LatLng(47.599221, -122.311531));
        points[7] = projection.toPoint(new LatLng(47.599663, -122.312410));
        points[8] = projection.toPoint(new LatLng(47.598823, -122.312614));
        points[9] = projection.toPoint(new LatLng(47.599959, -122.310651));

        return points;
    }

    @Override
    public Tile getTile(int x, int y, int zoom) {
        Bitmap bitmap = Bitmap.createBitmap(tileSizeScaled, tileSizeScaled, Bitmap.Config.ARGB_8888);
        float scale = (float) (Math.pow(2, zoom) * density);

        Canvas c = new Canvas(bitmap);
        Matrix m = new Matrix();
        m.setScale(scale, scale);
        m.postTranslate(-x * tileSizeScaled, -y * tileSizeScaled);

        c.setMatrix(m);

        pathPaint.setStrokeWidth(6 * density / scale);
        c.drawPath(path, pathPaint);
        return bitmapToTile(bitmap);
    }

    private Tile bitmapToTile(Bitmap bmp) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
        byte[] bitmapdata = stream.toByteArray();
        return new Tile(tileSizeScaled, tileSizeScaled, bitmapdata);
    }
}

1 个答案:

答案 0 :(得分:1)

我看到你正在使用google的tileview,你可以尝试考虑mogarius的库,这是github上的开源
我以前从未尝试过它,但是它支持你需要的大部分功能(标记\点
和动态路径绘图),这样可以节省你花在制作上的时间用于放大和缩小的矩阵计算。
还有一个演示video用于他所做的一些用法,以及他发表的一个很棒的javadoc