使用本地瓷砖的瓷砖提供程序

我想使用最新的 Android Maps API (v2)的新的 TileProvider功能来覆盖 GoogleMap上的一些自定义贴片。然而,由于我的用户不会有互联网的很多时间,我想保持瓷砖存储在一个 zipfile/文件夹结构的设备上。我将使用 Maptilergeotiffs生成我的瓷砖。我的问题是:

  1. 在设备上存储瓷砖的最佳方式是什么?
  2. 我该如何创建一个返回本地贴片的 TileProvider 呢?
67654 次浏览
  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);
...
}

[ Upd ]

如果您知道自定义地图的确切区域,那么对于 getTile(...)方法中缺少的贴图,应该返回 NO_TILE

我是这么做的:

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

在新的 API (v2)中添加自定义瓦片提供程序的可能性很大,但是您提到您的用户大多数是离线的。如果用户在首次启动应用程序时处于离线状态,则不能使用新的 API,因为它要求用户处于在线状态(似乎至少一次是为了构建缓存) ,否则它将只显示黑屏。

编辑2/22-14: 我最近又遇到了同样的问题——为一个必须离线工作的应用程序定制瓷砖。通过将一个不可见的(w/h 0/0)映射视图添加到客户机必须下载一些内容的初始视图,解决了这个问题。这似乎可以工作,并允许我稍后在脱机模式下使用 mapview。

我在 Kotlin 就是这样做的:

class LocalTileProvider : TileProvider
{


override fun getTile(x: Int, y: Int, zoom: Int): Tile?
{
// This is for my case only
if (zoom > 11)
return TileProvider.NO_TILE


val path = "${getImagesFolder()}/tiles/$zoom/$x/$y/filled.png"
val file = File(path)
if (!file.exists())
return TileProvider.NO_TILE
return try {
Tile(TILE_SIZE, TILE_SIZE, file.readBytes())
}
catch (e: Exception)
{
TileProvider.NO_TILE
}
}


companion object {
const val TILE_SIZE = 512
}


}