TileProvider используя местные плитки


Я хотел бы использовать новую функциональность TileProvider последнего Android Maps API (v2) для наложения некоторых пользовательских плиток на GoogleMap. Однако, поскольку мои пользователи не будут иметь интернет много времени, я хочу сохранить плитки, хранящиеся в структуре zipfile / folder на устройстве. Я буду генерировать свои плитки, используя Maptiler с geotiffs. Мои вопросы таковы:

  1. Как лучше всего хранить плитки на устройстве?
  2. Как я буду создавать TileProvider, который возвращает local плитки?
2 64

2 ответа:

  1. Вы можете поместить плитки в папку assets (если это приемлемо для размера приложения) или загрузить их все при первом запуске и поместить их в хранилище устройства (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);
}

В моем случае у меня также была проблема с координатами 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);
    ...
}

[Upd]

Если вы знаете область exac вашей пользовательской карты, вы должны вернуть 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);
}

Возможность добавления пользовательских tileproviders в новом API (v2) велика, однако вы упоминаете, что ваши пользователи в основном находятся в автономном режиме. Если пользователь находится в автономном режиме при первом запуске приложения, вы не можете использовать новый API, поскольку он требует, чтобы пользователь был в сети (по крайней мере, один раз, чтобы построить кэш, кажется) - в противном случае он будет отображать только черный экран.

Правка 2/22-14: Недавно я снова столкнулся с той же проблемой - наличие пользовательских плиток для приложения, которое должно было работать в автономном режиме. Решил ее путем добавление невидимой (ш/0/0) положение в первоначальный вид, когда клиент пришлось скачать какой-нибудь контент. Это, кажется, работает, и позволяет мне использовать mapview в автономном режиме позже.