TileProvider с использованием локальных плиток
Я хотел бы использовать новый TileProvider
функциональность последней версии API Карт Google (v2) для наложения некоторых пользовательских плиток на GoogleMap
, Однако, поскольку у моих пользователей не будет Интернета много времени, я хочу, чтобы плитки сохранялись в структуре zipfile/folder на устройстве. Я буду генерировать свои плитки, используя Maptiler
с geotiffs
, Мои вопросы:
- Как лучше всего хранить плитки на устройстве?
- Как бы я пошел о создании TileProvider, который возвращает локальные плитки?
3 ответа
Вы можете поместить плитки в папку активов (если это приемлемо для размера приложения) или загрузить их все при первом запуске и поместить их в память устройства (SD-карта).
Вы можете реализовать 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);
}
В моем случае у меня также была проблема с координатой y плиток, сгенерированных MapTiler, но я справился с этим, добавив этот метод в 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);
...
}
[Обно]
Если вы знаете точную область вашей пользовательской карты, вы должны вернуть NO_TILE
за отсутствующие плитки от getTile(...)
метод.
Вот как я это сделал:
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) представление карты в начальный вид, где клиент должен был загрузить некоторый контент. Это, кажется, работает, и позволяет мне позже использовать вид карты в автономном режиме.
Вот как я реализовал это в Котлине:
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
}
}