TileProvider używając lokalnych płytek

Chciałbym użyć nowej funkcjonalności TileProvider najnowszego API Map Android (v2), aby nałożyć kilka niestandardowych płytek na GoogleMap. Jednak ponieważ moi użytkownicy nie będą mieli Internetu przez wiele czasu, chcę zachować płytki przechowywane w strukturze plików/folderów zipfile na urządzeniu. Będę generował moje płytki używając Maptiler z geotiffs. Moje pytania to:

  1. jaki byłby najlepszy sposób przechowywania płytek na urządzeniu?
  2. jak mógłbym stworzyć TileProvider, który zwraca lokalny płytki?
Author: nkorth, 2013-02-09

2 answers

  1. Możesz umieścić kafelki w folderze zasoby (jeśli jest to dopuszczalne dla rozmiaru aplikacji) lub pobrać je wszystkie przy pierwszym uruchomieniu i umieścić je w pamięci urządzenia (Karta SD).

  2. Możesz zaimplementować TileProvider w następujący sposób:


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

I teraz możesz go używać z instancją 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);
}

W moim przypadku miałem również problem ze współrzędną y kafelków generowanych przez Maptilera, ale udało mi się to dodając tę metodę do 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;
}

I wywołaj go z metody getTile () w następujący sposób:

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

[Upd]

Jeśli znasz obszar exac swojej niestandardowej mapy, powinieneś zwrócić NO_TILE za brakujące płytki z metody getTile(...).

Tak to zrobiłem:

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);
}
 160
Author: Alex Vasilkov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-25 10:04:16

Możliwość dodawania niestandardowych tileproviderów w nowym API (v2) jest świetna, jednak wspominasz, że Twoi użytkownicy są w większości offline. Jeśli użytkownik jest w trybie offline przy pierwszym uruchomieniu aplikacji, nie możesz użyć nowego API, ponieważ wymaga on od użytkownika bycia online ( przynajmniej raz, aby zbudować pamięć podręczną, jak się wydaje) - w przeciwnym razie wyświetli tylko czarny ekran.

EDIT 2/22-14: Niedawno natknąłem się ponownie na ten sam problem - posiadanie niestandardowych płytek dla aplikacji, która musiała działać w trybie offline. Rozwiązany przez dodanie niewidocznego (w/h 0/0) mapview do początkowego widoku, w którym klient musiał pobrać część zawartości. To wydaje się działać i pozwala mi korzystać z mapview w trybie offline później.

 7
Author: erik_beus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-22 09:48:01