【问题标题】:TileProvider graphics gets skewed on higher zoom levelsTileProvider 图形在更高的缩放级别上偏斜
【发布时间】:2014-08-11 20:27:19
【问题描述】:

我目前在 Android Maps API v2 中使用 TileProver 并且有点卡住以下问题:我手动绘制到位图中的图形在更高的缩放级别上会显着倾斜:

让我解释一下我在这里做什么。我有许多 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,这是巨大的。就像用巨大的放大镜看这个圆圈。这就是为什么我有这个效果。

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

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

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

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

更新 即使我想出了如何翻译单个点并避免位图缩放:

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

我仍然不知道如何处理 Path 对象。我无法像使用单个点那样平移/缩放路径,所以我仍然必须缩放我的位图,这会再次导致绘图伪影(笔划宽度在更高的缩放级别上偏斜):

这是一个代码sn-p:

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

【问题讨论】:

  • 提出一个新问题,因为您已经回答了原来的问题。我只是浪费时间写下你如何绘制非常小的圆圈并缩放它们导致锯齿状,但看起来你已经发现了这一点。
  • 你能找到方法吗?

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


【解决方案1】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多