TileProvider 使用本地图块

Posted

技术标签:

【中文标题】TileProvider 使用本地图块【英文标题】:TileProvider using local tiles 【发布时间】:2013-01-24 22:38:17 【问题描述】:

我想使用最新 android Maps API (v2) 的新 TileProvider 功能在 GoogleMap 上叠加一些自定义图块。但是,由于我的用户很多时候不会有互联网,我想将磁贴存储在设备上的 zipfile/文件夹结构中。我将使用Maptilergeotiffs 生成我的图块。我的问题是:

    在设备上存储磁贴的最佳方式是什么? 我将如何创建一个返回本地图块的 TileProvider?

【问题讨论】:

同样我想知道一件事,Google Map V2 是否提供下载/缓存图块的工具? (prntscr.com/3cyiqf) b'cos 在这种情况下我很困惑,这意味着如果他们提供了如何使用 TileProvider 类加载/使用图块,那么它应该是可用于图块缓存/下载的东西。我的实际要求是我需要根据用户的要求下载/缓存地图。我已经检查过 OSMDROID 库,但我只想使用 google map v2 @Rajan 我偶然发现了同样的问题。似乎可以使用 tileProvide 进行缓存。你决定用什么? @Gyroscope,你从哪里得到这些瓷砖?可以下载吗? 【参考方案1】:

    您可以将磁贴放入资产文件夹(如果应用大小可以接受)或在首次启动时将它们全部下载并放入设备存储(SD 卡)中。

    您可以像这样实现 TileProvider:


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

现在您可以将它与您的 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);

在我的情况下,我也遇到了 MapTiler 生成的瓦片的 y 坐标问题,但我通过将此方法添加到 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;

并像这样从 getTile() 方法调用它:

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

[更新]

如果您知道自定义地图的精确区域,则应从 getTile(...) 方法返回 NO_TILE 以获取缺失的图块。

我就是这样做的:

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

【讨论】:

请注意getTile(int,int,int)的返回值的文档:用于这个瓦片坐标的瓦片。如果您不希望为此图块坐标提供图块,请返回 NO_TILE。如果此时无法找到该图块,则返回 null 并且可能会使用指数退避进行进一步的请求。 感谢@JesperB,请参阅答案的[Upd]部分 谢谢 - 我有一个笨拙的版本,是我在 API v2 首次发布时编写的,您的示例极大地提高了我的性能。 FWIW,我的瓦片集没有形成一个完美的矩形,所以我通过检查输入流是否为空来检查瓦片的存在。 Alex Vasilkov : in `CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(LAT, LON), ZOOM);` 我必须通过哪个 LAT、LAN? 请问如何知道您在 SparseArray 中输入的值? . Rect() 中这四个值是什么?【参考方案2】:

在新 API (v2) 中添加自定义 tileproviders 的可能性很大,但是您提到您的用户大多处于离线状态。如果用户在首次启动应用程序时处于离线状态,您将无法使用新 API,因为它要求用户在线(似乎至少要建立一次缓存)——否则只会显示黑屏。

编辑 2/22-14: 我最近再次遇到了同样的问题 - 为必须离线工作的应用程序设置自定义磁贴。通过将不可见的(w/h 0/0)地图视图添加到客户端必须下载一些内容的初始视图来解决它。这似乎可行,并允许我稍后在离线模式下使用地图视图。

【讨论】:

莫蒂,从哪里得到这些信息?这是谷歌的官方意图吗?这将导致无法在没有互联网连接的情况下使用 gmap api v2 离线显示本地存储的地图。汤姆 @Tom 恐怕他是对的。是的,这真的很烦人。 在这种情况下,您需要将“ZoomTables.data”(初始化地图时从模拟器中拉出)复制到/data/data//files/ 有人可以详细说明在飞行模式下首次启动时加载自定义地图图块。 我有自定义图块,需要显示它们,但 getTile() 在第一次启动时不会被调用。我需要在飞行模式下首次启动时显示自定义图块。【参考方案3】:

这就是我在 Kotlin 中的实现方式:

class LocalTileProvider : TileProvider


override fun getTile(x: Int, y: Int, zoom: Int): Tile?

    // This is for my case only
    if (zoom > 11)
        return TileProvider.NO_TILE

    val path = "$getImagesFolder()/tiles/$zoom/$x/$y/filled.png"
    val file = File(path)
    if (!file.exists())
        return TileProvider.NO_TILE
    return try 
        Tile(TILE_SIZE, TILE_SIZE, file.readBytes())
    
    catch (e: Exception)
    
        TileProvider.NO_TILE
    


companion object 
    const val TILE_SIZE = 512



【讨论】:

以上是关于TileProvider 使用本地图块的主要内容,如果未能解决你的问题,请参考以下文章

在 iOS 上 react-native-maps 本地图块/离线

TileProvider 方法 getTile - 需要将 x 和 y 转换为 lat/long

如何使用 Provider 实现 Flutter 本地化?

黑色图块在UIWebview中以pdf缩放显示

2017-02-24:IDEA本地调用provider方法

CAD如何创建内部图块