使用本地图块的TileProvider


72

我想使用TileProvider最新的Android Maps API(v2)的新功能在上覆盖一些自定义图块GoogleMap。但是,由于我的用户很多时候都无法上网,因此我希望将图块保存在设备上的zipfile / folder结构中。我将使用Maptiler和来生成我的图块geotiffs。我的问题是:

  1. 将磁贴存储在设备上的最佳方法是什么?
  2. 如何创建返回本地图块的TileProvider?

同样,我想知道1件事,Google Map V2是否提供下载/缓存图块的功能?(prntscr.com/3cyiqf)在这种情况下,我很困惑,这意味着如果他们提供如何使用TileProvider类加载/使用图块,那么它应该适用于图块缓存/下载。我的实际要求是我需要根据用户要求下载/缓存地图。我已经检查过OSMDROID lib,但只想使用Google Map v2
Rajan

@Rajan我偶然发现了同样的问题。似乎可以使用tileProvide进行缓存。您决定使用什么?
ar-g

@陀螺仪,您从哪里得到这些瓷砖的?它们可以下载吗?
鹤山桑迪帕

Answers:


175
  1. 您可以将图块放入资源文件夹(如果应用程序大小可接受),也可以在首次启动时将其全部下载,然后将其放入设备存储设备(SD卡)中。

  2. 您可以像这样实现TileProvider:


public class CustomMapTileProvider implements TileProvider {
    private static final int TILE_WIDTH = 256;
    private static final int TILE_HEIGHT = 256;
    private static final int BUFFER_SIZE = 16 * 1024;

    private AssetManager mAssets;

    public CustomMapTileProvider(AssetManager assets) {
        mAssets = assets;
    }

    @Override
    public Tile getTile(int x, int y, int zoom) {
        byte[] image = readTileImage(x, y, zoom);
        return image == null ? null : new Tile(TILE_WIDTH, TILE_HEIGHT, image);
    }

    private byte[] readTileImage(int x, int y, int zoom) {
        InputStream in = null;
        ByteArrayOutputStream buffer = null;

        try {
            in = mAssets.open(getTileFilename(x, y, zoom));
            buffer = new ByteArrayOutputStream();

            int nRead;
            byte[] data = new byte[BUFFER_SIZE];

            while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();

            return buffer.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } catch (OutOfMemoryError e) {
            e.printStackTrace();
            return null;
        } finally {
            if (in != null) try { in.close(); } catch (Exception ignored) {}
            if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
        }
    }

    private String getTileFilename(int x, int y, int zoom) {
        return "map/" + zoom + '/' + x + '/' + y + ".png";
    }
}

现在,您可以将其与您的GoogleMap实例一起使用:

private void setUpMap() {
    mMap.setMapType(GoogleMap.MAP_TYPE_NONE);

    mMap.addTileOverlay(new TileOverlayOptions().tileProvider(new CustomMapTileProvider(getResources().getAssets())));

    CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(LAT, LON), ZOOM);
    mMap.moveCamera(upd);
}

在我的情况下,我还遇到了由MapTiler生成的图块的y坐标问题,但是我通过将此方法添加到CustomMapTileProvider中进行了管理:

/**
 * Fixing tile's y index (reversing order)
 */
private int fixYCoordinate(int y, int zoom) {
    int size = 1 << zoom; // size = 2^zoom
    return size - 1 - y;
}

然后从getTile()方法调用它,如下所示:

@Override
public Tile getTile(int x, int y, int zoom) {
    y = fixYCoordinate(y, zoom);
    ...
}

[更新]

如果您知道自定义地图的exac区域,则应NO_TILEgetTile(...)方法中返回缺少的图块。

这是我的方法:

private static final SparseArray<Rect> TILE_ZOOMS = new SparseArray<Rect>() {{
    put(8,  new Rect(135,  180,  135,  181 ));
    put(9,  new Rect(270,  361,  271,  363 ));
    put(10, new Rect(541,  723,  543,  726 ));
    put(11, new Rect(1082, 1447, 1086, 1452));
    put(12, new Rect(2165, 2894, 2172, 2905));
    put(13, new Rect(4330, 5789, 4345, 5810));
    put(14, new Rect(8661, 11578, 8691, 11621));
}};

@Override
public Tile getTile(int x, int y, int zoom) {
    y = fixYCoordinate(y, zoom);

    if (hasTile(x, y, zoom)) {
        byte[] image = readTileImage(x, y, zoom);
        return image == null ? null : new Tile(TILE_WIDTH, TILE_HEIGHT, image);
    } else {
        return NO_TILE;
    }
}

private boolean hasTile(int x, int y, int zoom) {
    Rect b = TILE_ZOOMS.get(zoom);
    return b == null ? false : (b.left <= x && x <= b.right && b.top <= y && y <= b.bottom);
}

1
请注意getTile(int,int,int)返回值的文档:用于此tile坐标的Tile。如果您不希望为此图块坐标提供图块,请返回NO_TILE。如果在此时无法找到该图块,则返回null,并且可能会以指数补偿的方式进行进一步的请求。
JesperB

感谢@JesperB,请查看答案的[Upd]部分
Alex Vasilkov

谢谢-我在API v2首次发布时编写了一个笨拙的版本,您的示例极大地提高了我的性能。FWIW,我的图块集没有形成完美的矩形,因此我通过检查inputstream是否为null来检查图块是否存在。
alice_silver_man

Alex Vasilkov:在`CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(LAT,LON),ZOOM);中,我必须通过哪个LAT,LAN?
Harshal Kalavadiya 2015年

1
请问如何知道您在SparseArray中输入的值?。Rect()中的这四个值是什么?
azmuhak '16

8

在新API(v2)中添加自定义tileproviders的可能性很大,但是您提到用户大多处于脱机状态。如果用户在首次启动应用程序时处于脱机状态,则您将无法使用新的API,因为新API要求用户处于在线状态(看来至少要建立一次缓存),否则它将仅显示黑屏。

编辑2 / 22-14:我最近又遇到了同样的问题-为必须脱机工作的应用程序定制了磁贴。通过在客户端必须下载某些内容的初始视图中添加不可见的(w / h 0/0)mapview来解决该问题。这似乎可行,以后我可以在离线模式下使用mapview。


2
莫蒂,从哪里得到这些信息?这是Google的官方意图吗?这将导致无法使用gmap api v2在没有互联网连接的情况下离线显示本地存储的maptile。汤姆
汤姆

@汤姆,我怕他是对的。是的,这真的很忙。
Warpzit 2014年

在这种情况下,您需要将“ ZoomTables.data”(在初始化地图时从模拟器中拉出)复制到/ data / data / <app_name> / files /
Yuraj,2016年

可以详细说明如何在飞机模式下首次加载自定义地图图块。
abhishek

我有自定义图块,需要显示它们,但第一次启动时不会调用getTile()。我需要在飞机模式下首次启动时显示自定义图块。
abhishek
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.