TileProvider using local tiles

前端 未结 2 1269
难免孤独
难免孤独 2020-11-22 15:03

I would like to use the new TileProvider functionality of the latest Android Maps API (v2) to overlay some custom tiles on the GoogleMap. However a

相关标签:
2条回答
  • 2020-11-22 15:21

    The possibility of adding custom tileproviders in the new API (v2) is great, however you mention that your users are mostly offline. If a user is offline when first launching the application you cannot use the new API as it requires the user to be online (at least once to build a cache it seems) - otherwise it will only display a black screen.

    EDIT 2/22-14: I recently came across the same issue again - having custom tiles for an app which had to work offline. Solved it by adding an invisible (w/h 0/0) mapview to an initial view where the client had to download some content. This seems to work, and allows me to use a mapview in offline mode later on.

    0 讨论(0)
  • 2020-11-22 15:26
    1. You can put tiles into assets folder (if it is acceptable for the app size) or download them all on first start and put them into device storage (SD card).

    2. You can implement TileProvider like this:


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

    And now you can use it with your GoogleMap instance:

    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 my case I also had a problem with y coordinate of tiles generated by MapTiler, but I managed it by adding this method into 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;
    }
    

    and callig it from getTile() method like this:

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

    [Upd]

    If you know exac area of your custom map, you should return NO_TILE for missing tiles from getTile(...) method.

    This is how I did it:

    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);
    }
    
    0 讨论(0)
提交回复
热议问题