web-dev-qa-db-fra.com

TileProvider utilisant des tuiles locales

Je voudrais utiliser la nouvelle fonctionnalité TileProvider de la dernière Android API de cartes (v2) pour superposer certaines tuiles personnalisées sur le GoogleMap. Cependant, en tant que mes utilisateurs Internet n’a pas souvent accès à Internet, je souhaite conserver les vignettes stockées dans une structure zipfile/dossier de l’appareil. Je vais générer mes vignettes avec Maptiler avec geotiffs. Mes questions sont:

  1. Quel serait le meilleur moyen de stocker les tuiles sur l'appareil?
  2. Comment pourrais-je créer un TileProvider qui renvoie les tuiles locales?
70
Gyroscope
  1. Vous pouvez placer les vignettes dans le dossier des éléments (si la taille de l'application le permet,) ou bien les télécharger toutes au premier démarrage et les mettre dans le stockage de l'appareil (carte SD).

  2. Vous pouvez implémenter TileProvider comme ceci:


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

Et maintenant, vous pouvez l'utiliser avec votre instance 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);
}

Dans mon cas, j'ai également eu un problème avec la coordonnée y des tuiles générées par MapTiler, mais je l'ai géré en ajoutant cette méthode à 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;
}

et l'appelez depuis la méthode getTile () comme ceci:

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

[Upd]

Si vous connaissez la zone exac de votre carte personnalisée, vous devez renvoyer NO_TILE Pour les carreaux manquants de la méthode getTile(...).

Voici comment je l'ai fait:

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);
}
171
Alex Vasilkov

La possibilité d'ajouter des fournisseurs de pavés personnalisés dans la nouvelle API (v2) est excellente, mais vous indiquez que vos utilisateurs sont généralement hors ligne. Si un utilisateur est hors ligne lors du premier lancement de l'application, vous ne pouvez pas utiliser la nouvelle API, car celle-ci doit être en ligne (au moins une fois pour créer un cache, semble-t-il). Dans le cas contraire, elle ne fera apparaître qu'un écran noir.

EDIT 2/22-14: Je suis récemment tombé sur le même problème: créer des mosaïques personnalisées pour une application qui devait fonctionner hors connexion. Résolu ce problème en ajoutant une vue cartographique invisible (w/h 0/0) à une vue initiale dans laquelle le client devait télécharger du contenu. Cela semble fonctionner et me permet d’utiliser une vue cartographique en mode hors connexion ultérieurement.

8
erik_beus