android - TileProvider 图形在更高的缩放级别上倾斜

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

我目前正在玩 TileProver在 Android Maps API v2 中遇到以下问题:我手动绘制到位图中的图形在较高的缩放级别上会明显倾斜:

graphics is skewed

让我解释一下我在这里做什么。我有多个 LatLng 点,我为 map 上的每个点画了一个圆圈,所以当你放大时 - 点保持在相同的地理位置。正如您在屏幕截图中看到的那样,圆圈在较低的缩放级别上看起来很好,但是当您开始放大时 - 圆圈会发生倾斜。

它是这样实现的:

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 个图 block (256x256,即缩放级别 0)的屏幕位置,然后为了将此屏幕点转换为其他缩放级别,我需要缩放我的位图并将其转换到适当的位置。同时,由于位图被缩放,我需要补偿圆半径,所以我用比例因子除以半径。所以在缩放级别 19 我的比例因子已经是 1572864 这是巨大的。就像用巨大的放大镜看这个圆圈。这就是为什么我有这种效果。

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

不幸的是,矩阵数学不是我最强的技能,所以我的问题是 - 如何缩放/转换任意缩放级别的点集,并为缩放级别“0”计算点集?

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

注意 请注意,我需要专门自定义 TileProvider,因为在应用程序中我将绘制比圆圈更复杂的图 block 。如此简单的 Marker 类在这里对我不起作用

更新 尽管我想出了如何平移单个点并避免位图缩放:

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

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

enter image description here

这是一个代码 fragment :

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);
    }
}

最佳答案

我看到您正在使用 google 的 tileview,您可以尝试考虑 mogarius 的库,它是一个
github 上开源的库

我以前从未尝试过,但它开箱即用地支持你需要的大部分功能(标记\点
和动态路径绘制),这样可以节省你制作
的时间用于放大和缩小的矩阵计算。
还有一个演示video对于他所做的一些用法,以及很棒的 javadoc他出版了。

关于android - TileProvider 图形在更高的缩放级别上倾斜,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25251831/

相关文章:

Android 谷歌地图透明度

android - 每天在应用未打开时运行代码

c++ - 如何在gdi+中绘制 "pouring"效果?

opengl - 绘制3D孔的图形技术是什么?

安卓.view.InflateException : Binary XML file line #13: Error inflating class fragment

android - 向标记添加自定义属性(Google Map Android API V2)

java - 在按钮上显示 float

java - 如何维护不同屏幕尺寸的不同值文件夹的大小

android - BluetoothAdapter 不会停止扫描 BLE 设备

Java:Linux 中的图形