TileProvider verwendet lokale Kacheln

72

Ich möchte die neue TileProviderFunktionalität der neuesten Android Maps API (v2) verwenden, um einige benutzerdefinierte Kacheln auf dem zu überlagern GoogleMap. Da meine Benutzer jedoch häufig nicht über das Internet verfügen, möchte ich die Kacheln in einer Zipfile- / Ordnerstruktur auf dem Gerät speichern. Ich werde meine Kacheln mit Maptilermit generieren geotiffs. Meine Fragen sind:

  1. Was wäre der beste Weg, um die Kacheln auf dem Gerät zu speichern?
  2. Wie würde ich einen TileProvider erstellen, der lokale Kacheln zurückgibt?
Gyroskop
quelle
In dem gleichen, was ich gerne wissen würde, bietet Google Map V2 eine Möglichkeit zum Herunterladen / Zwischenspeichern der Kachel? ( prntscr.com/3cyiqf ) b'cos Ich bin in diesem Fall verwirrt. Wenn sie angeben, wie Kacheln mit der TileProvider-Klasse geladen / verwendet werden sollen, sollte dies für das Zwischenspeichern / Herunterladen von Kacheln verfügbar sein. Meine eigentliche Anforderung ist, dass ich die Karte gemäß den Anforderungen des Benutzers herunterladen / zwischenspeichern muss. Ich habe bereits OSMDROID lib überprüft, aber ich möchte nur Google Map v2 verwenden
Rajan
@ Rajan Ich bin mit dem gleichen Problem gestolpert. Es scheint möglich zu sein, tileProvide zum Zwischenspeichern zu verwenden. Was hast du gewählt?
Ar-g
@ Gyroskop, Woher hast du diese Fliesen? stehen sie zum download zur Verfügung?
Heshan Sandeepa

Antworten:

174
  1. Sie können Kacheln in den Assets-Ordner legen (sofern dies für die App-Größe akzeptabel ist) oder sie alle beim ersten Start herunterladen und in den Gerätespeicher (SD-Karte) legen.

  2. Sie können TileProvider folgendermaßen implementieren:


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

Und jetzt können Sie es mit Ihrer GoogleMap-Instanz verwenden:

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

In meinem Fall hatte ich auch ein Problem mit der y-Koordinate der von MapTiler generierten Kacheln, aber ich habe es durch Hinzufügen dieser Methode zu CustomMapTileProvider verwaltet:

/**
 * 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;
}

und rufen Sie es von der getTile () -Methode wie folgt auf:

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

[Upd]

Wenn Sie den genauen Bereich Ihrer benutzerdefinierten Karte kennen, sollten Sie NO_TILEfür fehlende Kacheln aus der getTile(...)Methode zurückkehren.

So habe ich es gemacht:

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);
}
Alex Vasilkov
quelle
1
Bitte beachten Sie die Dokumentation des Rückgabewerts von getTile (int, int, int): die Kachel, die für diese Kachelkoordinate verwendet werden soll. Wenn Sie für diese Kachelkoordinate keine Kachel bereitstellen möchten, geben Sie NO_TILE zurück. Wenn die Kachel zu diesem Zeitpunkt nicht gefunden werden konnte, geben Sie null zurück, und weitere Anforderungen werden möglicherweise mit einem exponentiellen Backoff gestellt.
JesperB
Danke @JesperB, siehe [Upd] Abschnitt der Antwort
Alex Vasilkov
Vielen Dank - ich hatte eine klobige Version davon, die ich geschrieben habe, als API v2 zum ersten Mal herauskam, und Ihr Beispiel hat meine Leistung erheblich verbessert. FWIW, mein Kachelsatz bildet kein perfektes Rechteck, daher überprüfe ich die Existenz der Kachel, indem ich prüfe, ob der Eingabestream null ist.
Alice_Silver_man
Alex Vasilkov: in `CameraUpdate upd = CameraUpdateFactory.newLatLngZoom (neues LatLng (LAT, LON), ZOOM);` Welches LAT, LAN muss ich übergeben?
Harshal Kalavadiya
1
Darf ich fragen, wie man die Werte kennenlernt, die Sie in Ihr SparseArray eingegeben haben? . Was sind diese vier Werte in Rect ()?
Azmuhak
8

Die Möglichkeit, benutzerdefinierte Kachelanbieter in die neue API (v2) aufzunehmen, ist großartig. Sie erwähnen jedoch, dass Ihre Benutzer größtenteils offline sind. Wenn ein Benutzer beim ersten Start der Anwendung offline ist, können Sie die neue API nicht verwenden, da der Benutzer online sein muss (mindestens einmal, um einen Cache zu erstellen, wie es scheint). Andernfalls wird nur ein schwarzer Bildschirm angezeigt.

EDIT 2 / 22-14: Ich bin kürzlich wieder auf dasselbe Problem gestoßen - mit benutzerdefinierten Kacheln für eine App, die offline funktionieren musste. Es wurde behoben, indem einer unsichtbaren Ansicht, in der der Client Inhalte herunterladen musste, eine unsichtbare Kartenansicht (mit 0/0) hinzugefügt wurde. Dies scheint zu funktionieren und ermöglicht mir später, eine Kartenansicht im Offline-Modus zu verwenden.

erik_beus
quelle
2
Morty, woher bekommen diese Informationen? Ist dies die offizielle Absicht von Google? Dies würde es unmöglich machen, die gmap api v2 zum Offline-Anzeigen lokal gespeicherter Maptiles ohne Internetverbindung zu verwenden. Tom
Tom
@ Tom Ich fürchte, er hat recht. Ja, es ist wirklich nervig.
Warpzit
In dieser Situation müssen Sie "ZoomTables.data" (ziehen Sie es aus dem Emulator, wenn Sie die Karte initialisieren) nach / data / data / <app_name> / files /
Yuraj
Kann jemand näher auf das Laden der benutzerdefinierten Kartenkacheln eingehen, die zuerst im Flugzeugmodus gestartet werden?
Abhishek
Ich habe die benutzerdefinierten Kacheln, muss sie anzeigen, aber getTile () wird beim ersten Start nicht aufgerufen. Ich muss beim ersten Start im Flugzeugmodus benutzerdefinierte Kacheln anzeigen.
Abhishek