MauriF
MauriF

Reputation: 682

Caching a country OSM map in android for google maps api v2 tile provider

I simply want to use OSM maps cached in the device as a tile provider for google maps api v2. I have read this useful question, but I dont know how to cache the tiles.

I have my country's osm file, but how can i get an organized folder with all the png files?

If caching all png files in the device is too heavy, is there any library in android for getting the files directedly from the osm.pbf file?

Note that I want to keep using googles map api and not a replacement.

Upvotes: 3

Views: 1462

Answers (1)

N Dorigatti
N Dorigatti

Reputation: 3540

I'm not aware of pbf libraries (you should check OSMDroid if it is able). anyway, I succesfully managed to cache, by myself during runtime, the pngs coming from openstreetmap. This means that I've cached only the displayed tiles and not all of them in an area (I did a brute force download, but I don't recommend to do). I'll leave below the code for "caching" the live tiles, if you instead want to provide png together with the app, and then read them, you could try to put pngs in folder structure that have the first level as zoom level (Z), then put two other level for rows/latitude (y) and columns/longitude (x), and here place the file, better if the file has the three values as: Z_Y_X.png

You have to edit the "getTile" method for checking the PNG file in the folder structure created above.

Here it is instead my code for "live caching"

/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.jakewharton.disklrucache.DiskLruCache;

/**
 * Wrapper that provides a disk-based LRU cache for a TileProvider.
 *
 * @see DiskLruCache
 */
public class CachedTileProvider implements TileProvider {

    private static final String TAG = CachedTileProvider.class.getSimpleName();

    private static final String KEY_FORMAT = "%3$d_%2$d_%1$d_%4$s";

    // Index for cache entry streams
    private static final int INDEX_DATA = 0;
    private static final int INDEX_HEIGHT = 1;
    private static final int INDEX_WIDTH = 2;

    //TEST
    private static final double[] TILES_ORIGIN = {-20037508.34789244, 20037508.34789244};//TODO Duplicate from WMS PROVIDER, put as utils
    // Size of square world map in meters, using WebMerc projection.
    private static final double MAP_SIZE = 20037508.34789244 * 2;//TODO Duplicate from WMS PROVIDER, put as utils
    private static final double ORIGIN_SHIFT = Math.PI * 6378137d;
//TEST


    private final String mKeyTag;
    private final TileProvider mTileProvider;
    //private final DiskLruCache mCache;
    private DiskLruCache mCache;
    private Set<String> cachedStringsWMS = null;
    Context cont = null;

    /**
     * TileProvider that wraps another TileProvider and caches all Tiles in a DiskLruCache.
     * <p/>
     * <p>A {@link DiskLruCache} can be reused across multiple instances.
     * The keyTag is used to annotate entries for this TileProvider, it is recommended to use a unique
     * String for each instance to prevent collisions.
     * <p/>
     * <p>NOTE: The supplied {@link DiskLruCache} requires space for
     * 3 entries per cached object.
     *
     * @param keyTag       identifier used to identify tiles for this CachedTileProvider instance
     * @param tileProvider tiles from this TileProvider will be cached.
     * @param cache        the cache used to store tiles
     */
    public CachedTileProvider(String keyTag, TileProvider tileProvider, DiskLruCache cache, Context context) {
        mKeyTag = keyTag;
        mTileProvider = tileProvider;
        mCache = cache;
        cont = context;
    }

    public void setNewDiskCache(DiskLruCache cache) {
        mCache = cache;
    }

    /**
     * Load a tile.
     * If cached, the data for the tile is read from the underlying cache, otherwise the tile is
     * generated by the {@link com.google.android.gms.maps.model.TileProvider} and added to the
     * cache.
     *
     * @param x
     * @param y
     * @param zoom
     * @return
     */
    @Override
    public Tile getTile(int x, int y, int zoom) {
        final String key = CachedTileProvider.generateKey(x, y, zoom, mKeyTag);
        Tile tile = getCachedTile(key);
        if (tile == null) {
            // tile not cached, load from provider and then cache
            tile = mTileProvider.getTile(x, y, zoom);
            if (null != tile && cacheTile(key, tile)) {
                if (BuildConfig.DEBUG) Log.d(TAG, "Added tile to cache " + key);
            }
        }
        return tile;
    }

    /**
     * Load a tile from cache.
     * Returns null if there is no corresponding cache entry or it could not be loaded.
     *
     * @param key
     * @return
     */
    private Tile getCachedTile(String key) {
        try {
            DiskLruCache.Snapshot snapshot = mCache.get(key);
            if (snapshot == null) {
                // tile is not in cache
                return null;
            }

            final byte[] data = readStreamAsByteArray(snapshot.getInputStream(INDEX_DATA));
            final int height = readStreamAsInt(snapshot.getInputStream(INDEX_HEIGHT));
            final int width = readStreamAsInt(snapshot.getInputStream(INDEX_WIDTH));
            if (data != null) {
                if (BuildConfig.DEBUG)Log.d(TAG, "Cache hit for tile " + key);
                return new Tile(width, height, data);
            }

        } catch (IOException e) {
            // ignore error
            e.printStackTrace();
        } catch (IllegalStateException ise) {
            Log.e(TAG, "IllegalStateException thrown, maybe cache is closed: " + ise.getMessage(), ise);
        }
        return null;
    }

    private boolean cacheTile(String key, Tile tile) {
        try {
            DiskLruCache.Editor editor = mCache.edit(key);
            if (editor == null) {
                // editor is not available
                return false;
            }
            writeByteArrayToStream(tile.data, editor.newOutputStream(INDEX_DATA));
            writeIntToStream(tile.height, editor.newOutputStream(INDEX_HEIGHT));
            writeIntToStream(tile.width, editor.newOutputStream(INDEX_WIDTH));
            editor.commit();
            return true;
        } catch (IOException e) {
            // Tile could not be cached
            e.printStackTrace();
        } catch (IllegalStateException ise) {
            Log.e(TAG, "IllegalStateException thrown, maybe cache is closed: " + ise.getMessage(), ise);
        }
        return false;
    }       

    private static String generateKey(int x, int y, int zoom, String tag) {
        return String.format(KEY_FORMAT, x, y, zoom, tag);
    }

    private static void writeByteArrayToStream(byte[] data, OutputStream stream) throws IOException {
        try {
            stream.write(data);
        } finally {
            stream.close();
        }
    }

    private static void writeIntToStream(int data, OutputStream stream) throws IOException {
        DataOutputStream dos = new DataOutputStream(stream);
        try {
            dos.writeInt(data);
        } finally {
            try {
                dos.close();
            } finally {
                stream.close();
            }
        }
    }

    private static byte[] readStreamAsByteArray(InputStream inputStream) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int read = 0;
        byte[] data = new byte[1024];
        try {
            while ((read = inputStream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, read);
            }
        } finally {
            inputStream.close();
        }
        return buffer.toByteArray();
    }

    private static int readStreamAsInt(InputStream inputStream) throws IOException {
        DataInputStream buffer = new DataInputStream(inputStream);
        try {
            return buffer.readInt();
        } finally {
            inputStream.close();
        }
    }
}

Then you create an instance of that by wrapping up the Tile providers:

CachedTileProvider cachedTileProvider = new CachedTileProvider("osm", TileProviderFactory.getOSMBackGroundTileProvider(layerInfo.get(i)), mContext);

where

public static TileProvider getOSMBackGroundTileProvider() {
        TileProvider mOSMTileProvider = new UrlTileProvider(256, 256) {

                @Override
                public URL getTileUrl(int x, int y, int z) {
                    try {
                        String f = "http://a.tile.openstreetmap.org/%d/%d/%d.png";
                        return new URL(String.format(f, z, x, y));
                    } catch (MalformedURLException e) {
                        return null;
                    }
                }
            };

        return mOSMTileProvider;
    }

You have now to explore and check DiskLruCache library from jakewharton, but there's a lot of documentation for that, it would be better to open another question if needed.

Upvotes: 2

Related Questions