73 votes

TileProvider utilisant des tuiles locales

Je voudrais utiliser le nouveau TileProvider la fonctionnalité de la dernière Android Maps API (v2) pour superposer des tuiles personnalisées sur le GoogleMap . Cependant, comme mes utilisateurs n'auront pas accès à Internet la plupart du temps, je veux que les tuiles soient stockées dans une base de données. zipfile/folder sur l'appareil. Je vais générer mes tuiles en utilisant Maptiler con geotiffs . Mes questions sont les suivantes :

  1. Quelle serait la meilleure façon de stocker les tuiles sur l'appareil ?

  2. Comment puis-je créer un TileProvider qui renvoie des tuiles locales ?

Merci pour votre aide !

177voto

Alex Vasilkov Points 651
  1. Vous pouvez placer les tuiles dans le dossier des actifs (si la taille de l'application le permet) ou les télécharger toutes au premier démarrage et les placer 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 résolu en ajoutant cette méthode dans 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'appeler à partir de la méthode getTile() comme ceci :

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

[Mise à jour]

Si vous connaissez la zone exacte de votre carte personnalisée, vous devriez renvoyer l'information suivante NO_TILE pour les tuiles manquantes de getTile(...) méthode.

C'est comme ça que j'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);
}

8voto

Morty Points 76

La possibilité d'ajouter des fournisseurs de tuiles personnalisés dans la nouvelle API (v2) est excellente, mais vous mentionnez que vos utilisateurs sont le plus souvent hors ligne. Si un utilisateur est hors ligne lors du premier lancement de l'application, vous ne pouvez pas utiliser la nouvelle API car elle exige que l'utilisateur soit en ligne (au moins une fois pour construire un cache, semble-t-il) - sinon, elle affichera seulement un écran noir.

EDIT 2/22-14 : J'ai récemment rencontré le même problème - avoir des tuiles personnalisées pour une application qui devait fonctionner hors ligne. J'ai résolu le problème en ajoutant un aperçu de carte invisible (w/h 0/0) à une vue initiale où le client devait télécharger du contenu. Cela semble fonctionner, et me permet d'utiliser un aperçu de carte en mode hors ligne plus tard.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X