OSMDroid 从资产文件夹加载自定义离线图块

Posted

技术标签:

【中文标题】OSMDroid 从资产文件夹加载自定义离线图块【英文标题】:OSMDroid Loading Custom Offline tiles From Assets Folder 【发布时间】:2012-09-23 12:09:56 【问题描述】:

我想知道是否有可能做这样的事情。我知道需要修改一些现有代码才能实现这一点,但我想知道是否有人对在哪里查看以及如何执行此操作有任何指导。

我在地图上的特定区域放置了一些自定义图块,以替代 OSM 图块提供者,但需要将它们存储在 /assets/ 文件夹中。有什么想法吗?

【问题讨论】:

【参考方案1】:

我使用 nexts 类来做到这一点。

import java.io.InputStream;

import org.osmdroid.ResourceProxy.string;
import org.osmdroid.tileprovider.util.StreamUtils;

import android.content.res.AssetManager;
import android.graphics.drawable.Drawable;

public class AssetsTileSource extends CustomBitmapTileSourceBase 
        private final AssetManager mAssetManager;

        public AssetsTileSource(final AssetManager assetManager, final String aName, final string aResourceId,
                        final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels,
                        final String aImageFilenameEnding) 
                super(aName, aResourceId, aZoomMinLevel, aZoomMaxLevel, aTileSizePixels, aImageFilenameEnding);
                mAssetManager = assetManager;
        

        @Override
        public Drawable getDrawable(final String aFilePath) 
                InputStream inputStream = null;
                try 
                        inputStream = mAssetManager.open(aFilePath);
                        if (inputStream != null) 
                                final Drawable drawable = getDrawable(inputStream);
                                return drawable;
                        
                 catch (final Throwable e) 
                        // Tile does not exist in assets folder.
                        // Ignore silently
                 finally 
                        if (inputStream != null) 
                                StreamUtils.closeStream(inputStream);
                        
                

                return null;
        

MapTileFileAssetsProvider.java

public class MapTileFileAssetsProvider extends MapTileModuleProviderBase 

            protected ITileSource mTileSource;

            public MapTileFileAssetsProvider(final ITileSource pTileSource) 
                    super(OpenStreetMapTileProviderConstants.NUMBER_OF_TILE_FILESYSTEM_THREADS, OpenStreetMapTileProviderConstants.TILE_FILESYSTEM_MAXIMUM_QUEUE_SIZE);

                    mTileSource = pTileSource;
            

            @Override
            public boolean getUsesDataConnection() 
                    return false;
            

            @Override
            protected String getName() 
                    return "Assets Folder Provider";
            

            @Override
            protected String getThreadGroupName() 
                    return "assetsfolder";
            

            @Override
            protected Runnable getTileLoader() 
                    return new TileLoader();
            

            @Override
            public int getMinimumZoomLevel() 
                    return mTileSource != null ? mTileSource.getMinimumZoomLevel() : MAXIMUM_ZOOMLEVEL;
            

            @Override
            public int getMaximumZoomLevel() 
                    return mTileSource != null ? mTileSource.getMaximumZoomLevel() : MINIMUM_ZOOMLEVEL;
            

            @Override
            public void setTileSource(final ITileSource pTileSource) 
                    mTileSource = pTileSource;
            

            private class TileLoader extends MapTileModuleProviderBase.TileLoader 

                    @Override
                    public Drawable loadTile(final MapTileRequestState pState) throws CantContinueException 

                            if (mTileSource == null) 
                                    return null;
                            

                            final MapTile pTile = pState.getMapTile();
                            String path = mTileSource.getTileRelativeFilenameString(pTile);

                            Drawable drawable;
                            try 
                                    drawable = mTileSource.getDrawable(path);
                             catch (final LowMemoryException e) 
                                    // low memory so empty the queue
                                    throw new CantContinueException(e);
                            

                            return drawable;
                    
            
    

还有

import java.io.File;
import java.io.InputStream;
import java.util.Random;

import org.osmdroid.ResourceProxy;
import org.osmdroid.ResourceProxy.string;
import org.osmdroid.tileprovider.ExpirableBitmapDrawable;
import org.osmdroid.tileprovider.MapTile;
import org.osmdroid.tileprovider.constants.OpenStreetMapTileProviderConstants;
import org.osmdroid.tileprovider.tilesource.ITileSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;

public abstract class CustomBitmapTileSourceBase implements ITileSource,
                OpenStreetMapTileProviderConstants 

        private static final Logger logger = LoggerFactory.getLogger(CustomBitmapTileSourceBase.class);

        private static int globalOrdinal = 0;

        private final int mMinimumZoomLevel;
        private final int mMaximumZoomLevel;

        private final int mOrdinal;
        protected final String mName;
        protected final String mImageFilenameEnding;
        protected final Random random = new Random();

        private final int mTileSizePixels;

        private final string mResourceId;

        public CustomBitmapTileSourceBase(final String aName, final string aResourceId,
                        final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels,
                        final String aImageFilenameEnding) 
                mResourceId = aResourceId;
                mOrdinal = globalOrdinal++;
                mName = aName;
                mMinimumZoomLevel = aZoomMinLevel;
                mMaximumZoomLevel = aZoomMaxLevel;
                mTileSizePixels = aTileSizePixels;
                mImageFilenameEnding = aImageFilenameEnding;
        

        @Override
        public int ordinal() 
                return mOrdinal;
        

        @Override
        public String name() 
                return mName;
        

        public String pathBase() 
                return mName;
        

        public String imageFilenameEnding() 
                return mImageFilenameEnding;
        

        @Override
        public int getMinimumZoomLevel() 
                return mMinimumZoomLevel;
        

        @Override
        public int getMaximumZoomLevel() 
                return mMaximumZoomLevel;
        

        @Override
        public int getTileSizePixels() 
                return mTileSizePixels;
        

        @Override
        public String localizedName(final ResourceProxy proxy) 
                return proxy.getString(mResourceId);
        

        @Override
        public Drawable getDrawable(final String aFilePath) 
                try 
                        // default implementation will load the file as a bitmap and create
                        // a BitmapDrawable from it
                        final Bitmap bitmap = BitmapFactory.decodeFile(aFilePath);
                        if (bitmap != null) 
                                return new ExpirableBitmapDrawable(bitmap);
                         else 
                                // if we couldn't load it then it's invalid - delete it
                                try 
                                        new File(aFilePath).delete();
                                 catch (final Throwable e) 
                                        logger.error("Error deleting invalid file: " + aFilePath, e);
                                
                        
                 catch (final OutOfMemoryError e) 
                        logger.error("OutOfMemoryError loading bitmap: " + aFilePath);
                        System.gc();
                
                return null;
        

        @Override
        public String getTileRelativeFilenameString(final MapTile tile) 
                final StringBuilder sb = new StringBuilder();
                sb.append(pathBase());
                sb.append('/');
                sb.append(tile.getX());
                sb.append('_');
                sb.append(tile.getY());
                sb.append('_');
                sb.append(tile.getZoomLevel());
                sb.append(imageFilenameEnding());
                return sb.toString();
        


        @Override
        public Drawable getDrawable(final InputStream aFileInputStream) 
                try 
                        // default implementation will load the file as a bitmap and create
                        // a BitmapDrawable from it
                        final Bitmap bitmap = BitmapFactory.decodeStream(aFileInputStream);
                        if (bitmap != null) 
                                return new ExpirableBitmapDrawable(bitmap);
                        
                        System.gc();
                 catch (final OutOfMemoryError e) 
                        logger.error("OutOfMemoryError loading bitmap");
                        System.gc();
                        //throw new LowMemoryException(e);
                
                return null;
        

        public final class LowMemoryException extends Exception 
                private static final long serialVersionUID = 146526524087765134L;

                public LowMemoryException(final String pDetailMessage) 
                        super(pDetailMessage);
                

                public LowMemoryException(final Throwable pThrowable) 
                        super(pThrowable);
                
        

修改 getTileRelativeFilenameString() 方法以获取您的图块(我使用下一种格式:x_y_zoom.png)

例子:

mapView = new MapView(getApplicationContext(), 256);
mapView.setClickable(true);
mapView.setTag("Mapa");
mapView.setTileSource(TileSourceFactory.MAPNIK);
mapView.setMultiTouchControls(true);
mapView.setUseDataConnection(true);

MapTileModuleProviderBase moduleProvider = 
    new MapTileFileAssetsProvider(ASSETS_TILE_SOURCE);
SimpleRegisterReceiver simpleReceiver = 
    new SimpleRegisterReceiver(getApplicationContext());
MapTileProviderArray tileProviderArray = 
    new MapTileProviderArray(ASSETS_TILE_SOURCE, simpleReceiver, 
        new MapTileModuleProviderBase[]  moduleProvider );
TilesOverlay tilesOverlay = 
    new TilesOverlay(tileProviderArray, getApplicationContext());

mapView.getOverlays().add(tilesOverlay);

【讨论】:

如何在放大/缩小的同时应用坐标导航? 这些类很好用,但例子还不够完整。对于 ASSETS_TILE_SOURCE,它不是常量,而是来自 AssetsTileSource 类的新对象。创建 tileProviderArray 后,我在使用可以将其作为参数的构造函数创建 mapView 时使用 in ,例如MapView mapView = new MapView(this, 256, resourceProxy, tileProviderArray)。让 MapView 与图块源相关联对我来说更直观,而不是与叠加层相关联......也许只是我。 @jzafrila 感谢样品。 Bitmap.decodeStream() 不会在主 UI 线程上导致 GC_FOR_ALLOC 吗? @zIronManBox 对此不确定,我没有遇到任何问题,并且按预期工作。也许它可以改善这一点。【参考方案2】:

而不是直接从资产中读取,我将压缩的地图块(遵循 osmdroid 地图块目录结构格式)复制/部署到 osmdroid 地图块目录中,然后声明 3 个块提供者、存档、缓存和在线提供者。

public class MapTileProviderAssets extends MapTileProviderArray 
        implements IMapTileProviderCallback 

    private static final String LOG_TAG = "MapTileProviderAssets";

    private static final String ASSETS_MAP_DIRECTORY = "map";
    private static final String SDCARD_PATH = Environment.getExternalStorageDirectory().getPath();
    private static final String OSMDROID_MAP_FILE_SOURCE_DIRECTORY = "osmdroid";
    private static final String OSMDROID_MAP_FILE_SOURCE_DIRECTORY_PATH = 
            SDCARD_PATH + "/" + OSMDROID_MAP_FILE_SOURCE_DIRECTORY;

    public MapTileProviderAssets(final Context pContext) 
        this(pContext, TileSourceFactory.DEFAULT_TILE_SOURCE);
    

    public MapTileProviderAssets(final Context pContext, final ITileSource pTileSource) 
        this(pContext, new SimpleRegisterReceiver(pContext), 
                new NetworkAvailabliltyCheck(pContext), pTileSource);

    

    public MapTileProviderAssets(final Context pContext, final IRegisterReceiver pRegisterReceiver,
                                 final INetworkAvailablityCheck aNetworkAvailablityCheck, 
                                 final ITileSource pTileSource) 
        super(pTileSource, pRegisterReceiver);

        final TileWriter tileWriter = new TileWriter();

        // copy assets delivered in apk into osmdroid map source dir
        // load zip archive first, then cache, then online
        final List<String> zipArchivesRelativePathInAssets = 
                listArchives(pContext.getAssets(), ASSETS_MAP_DIRECTORY);
        for (final String zipFileRelativePathInAssets : zipArchivesRelativePathInAssets) 
            final String copiedFilePath = copyAssetFile(
                    pContext.getAssets(), zipFileRelativePathInAssets, 
                    OSMDROID_MAP_FILE_SOURCE_DIRECTORY);
            Log.d(LOG_TAG, String.format(
                    "Archive zip file copied into map source directory %s", copiedFilePath));
        
        // list zip files in map archive directory
        final Set<String> setZipFileArchivesPath = new HashSet<String>();
        FileTools.listFiles(setZipFileArchivesPath, new File(
                OSMDROID_MAP_FILE_SOURCE_DIRECTORY_PATH), ".zip", true);
        final Set<IArchiveFile> setZipFileArchives = new HashSet<IArchiveFile>();
        for (final String zipFileArchivesPath : setZipFileArchivesPath) 
            final File zipfile = new File(zipFileArchivesPath);
            final IArchiveFile archiveFile = ArchiveFileFactory.getArchiveFile(zipfile);
            if (archiveFile != null) 
                setZipFileArchives.add(archiveFile);
            
            setZipFileArchives.add(archiveFile);
            Log.d(LOG_TAG, String.format(
                    "Archive zip file %s added to map source ", zipFileArchivesPath));
        

        final MapTileFileArchiveProvider archiveProvider;
        Log.d(LOG_TAG, String.format(
                "%s archive zip files will be used as source", setZipFileArchives.size()));
        if (setZipFileArchives.size() > 0) 
            final IArchiveFile[] pArchives = 
                    setZipFileArchives.toArray(new IArchiveFile[setZipFileArchives.size()]);
            archiveProvider = new MapTileFileArchiveProvider(
                    pRegisterReceiver, pTileSource, pArchives);
         else 
            archiveProvider = new MapTileFileArchiveProvider(
                    pRegisterReceiver, pTileSource);
        
        mTileProviderList.add(archiveProvider);

        // cache
        final MapTileFilesystemProvider fileSystemProvider = 
                new MapTileFilesystemProvider(pRegisterReceiver, pTileSource);
        mTileProviderList.add(fileSystemProvider);

        // online tiles
        final MapTileDownloader downloaderProvider = 
                new MapTileDownloader(pTileSource, tileWriter, aNetworkAvailablityCheck);
        mTileProviderList.add(downloaderProvider);
    

    public static List<String> listArchives(final AssetManager assetManager, 
                                            final String subDirectory) 
        final List<String> listArchives = new ArrayList<String>();
        try 
            final String[] lstFiles = assetManager.list(subDirectory);
            if (lstFiles != null && lstFiles.length > 0) 
                for (final String file : lstFiles) 
                    if (isZip(file)) 
                        listArchives.add(subDirectory + "/" + file);
                    
                    // filter files (xxxxx.xxx format) and parse only directories, 
                    // with out this all files are parsed and
                    // the process is VERY slow
                    // WARNNING: we could have directories with dot for versioning
                    else if (isDirectory(file)) // (file.lastIndexOf(".") != (file.length() - 4)) 
                        listArchives(assetManager, subDirectory + "/" + file);
                    
                
            
         catch (final IOException e) 
            Log.w(LOG_TAG, String.format("List error: can't list %s, exception %s", 
                    subDirectory, Log.getStackTraceString(e)));
         catch (final Exception e) 
            Log.w(LOG_TAG, String.format("List error: can't list %s, exception %s", 
                    subDirectory, Log.getStackTraceString(e)));
        
        return listArchives;
    

    private static boolean isZip(final String file) 
        return file.endsWith(".zip");
    

    private static boolean isDirectory(final String file) 
        return file.lastIndexOf(".") != (file.length() - 4);
    

    private static String copyAssetFile(final AssetManager assetManager, 
                                        final String assetRelativePath,
                                        final String destinationDirectoryOnSdcard) 
        InputStream in = null;
        OutputStream out = null;
        final String newfilePath = SDCARD_PATH + "/" + 
                destinationDirectoryOnSdcard + "/" + assetRelativePath;
        final File newFile = new File(newfilePath);
        // copy file only if it doesn't exist yet
        if (!newFile.exists()) 
            Log.d(LOG_TAG, String.format(
                    "Copy %s map archive in assets into %s", assetRelativePath, newfilePath));
            try 
                final File directory = newFile.getParentFile();
                if (!directory.exists()) 
                    if (directory.mkdirs()) 
                        // Log.d(LOG_TAG, "Directory created: " + directory.getAbsolutePath());
                    
                
                in = assetManager.open(assetRelativePath);
                out = new FileOutputStream(newfilePath);
                copyFile(in, out);
                in.close();
                in = null;
                out.flush();
                out.close();
                out = null;
             catch (final Exception e) 
                Log.e(LOG_TAG, "Exception during copyAssetFile: " + Log.getStackTraceString(e));
            
        
        return newfilePath;
    

    private static void copyFile(final InputStream in, final OutputStream out) throws IOException 
        final byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) 
            out.write(buffer, 0, read);
        
    


【讨论】:

您的代码不完整。有一种方法不可访问:它是 FileTools.listFiles。我已经评论它并添加了这一行来替换它: setZipFileArchivesPath.add("name_of_your_map_file.zip"); ....我仍然不确定是否必须包含字符串 .zip 作为文件名。我会自己检查。 是的,FileTools.listFiles 的代码不存在,因为它很容易实现。该方法只是简单地列出输入的地图存档目录的所有 zip 文件。

以上是关于OSMDroid 从资产文件夹加载自定义离线图块的主要内容,如果未能解决你的问题,请参考以下文章

osmdroid:加载自定义tilesource时显示的乱码

使用 osmdroid 覆盖项目的自定义信息气泡

从颤振资产加载 .pdf 文件/自定义文件

osmdroid离线地图不加载瓷砖

OSMDROID - 加载从 mapsforge 下载的 .map 文件

如何离线使用 MOBAC 创建的 OSMDroid SQLite 瓦片源文件?