TileProvider usando azulejos locales


Me gustaría usar la nueva funcionalidad TileProvider de la última API de Android Maps (v2) para superponer algunos mosaicos personalizados en GoogleMap. Sin embargo, como mis usuarios no tendrán Internet mucho tiempo, quiero mantener los mosaicos almacenados en una estructura de archivo/carpeta zipfile en el dispositivo. Voy a generar mis fichas usando Maptiler con geotiffs. Mis preguntas son:

  1. ¿Cuál sería la mejor manera de almacenar los azulejos en el dispositivo?
  2. ¿Cómo podría crear un TileProvider que devuelve local azulejos?
Author: nkorth, 2013-02-09

2 answers

  1. Puede poner los mosaicos en la carpeta assets (si es aceptable para el tamaño de la aplicación) o descargarlos todos en el primer inicio y ponerlos en el almacenamiento del dispositivo (tarjeta SD).

  2. Puedes implementar TileProvider así:


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

Y ahora puede usarlo con su instancia 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);
}

En mi caso también tuve un problema con la coordenada y de los mosaicos generados por MapTiler, pero lo manejé agregando este método a 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;
}

Y lo llama desde el método GetTile() así:

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

[Upd]

Si conoce el área exac de su mapa personalizado, debe devolver NO_TILE para los mosaicos faltantes del método getTile(...).

Así es como lo hice: {[10]]}

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-25 10:04:16

La posibilidad de agregar tileproviders personalizados en la nueva API (v2) es genial, sin embargo, mencionas que tus usuarios están en su mayoría fuera de línea. Si un usuario está fuera de línea al iniciar la aplicación por primera vez, no puede usar la nueva API, ya que requiere que el usuario esté en línea (al menos una vez para construir una caché, parece); de lo contrario, solo mostrará una pantalla en negro.

EDITAR 2/22-14: Recientemente me encontré con el mismo problema de nuevo-tener azulejos personalizados para una aplicación que tenía que trabajar sin conexión. Resuelto por agregar una vista de mapa invisible (w/h 0/0) a una vista inicial donde el cliente tuvo que descargar algún contenido. Esto parece funcionar, y me permite usar una vista de mapa en modo sin conexión más adelante.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-22 09:48:01