在osmdroid中支持多种离线地图格式,其中sqlite,mbtiles,zip,gemf等格式,那么他们的格式有什么不同呢?
下面我们慢慢来研究一下源代码既可以知道。
针对ArchiveFileFactory的类查看如下:
package org.osmdroid.tileprovider.modules; import java.io.File; import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import android.database.sqlite.SQLiteException; public class ArchiveFileFactory { private static final Logger logger = LoggerFactory.getLogger(ArchiveFileFactory.class); /** * Return an implementation of {@link IArchiveFile} for the specified file. * @return an implementation, or null if there's no suitable implementation */ public static IArchiveFile getArchiveFile(final File pFile) { if (pFile.getName().endsWith(".zip")) { try { return ZipFileArchive.getZipFileArchive(pFile); } catch (final IOException e) { logger.error("Error opening ZIP file", e); } } if (pFile.getName().endsWith(".sqlite")) { try { return DatabaseFileArchive.getDatabaseFileArchive(pFile); } catch (final SQLiteException e) { logger.error("Error opening SQL file", e); } } if (pFile.getName().endsWith(".mbtiles")) { try { return MBTilesFileArchive.getDatabaseFileArchive(pFile); } catch (final SQLiteException e) { logger.error("Error opening MBTiles SQLite file", e); } } if (pFile.getName().endsWith(".gemf")) { try { return GEMFFileArchive.getGEMFFileArchive(pFile); } catch (final IOException e) { logger.error("Error opening GEMF file", e); } } return null; } }
其中sqlite格式如下:存储的表名为:tiles字段为tile,key,provider,
具体实现如下:
package org.osmdroid.tileprovider.modules; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.tilesource.ITileSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; public class DatabaseFileArchive implements IArchiveFile { private static final Logger logger = LoggerFactory.getLogger(DatabaseFileArchive.class); private final SQLiteDatabase mDatabase; private DatabaseFileArchive(final SQLiteDatabase pDatabase) { mDatabase = pDatabase; } public static DatabaseFileArchive getDatabaseFileArchive(final File pFile) throws SQLiteException { return new DatabaseFileArchive(SQLiteDatabase.openOrCreateDatabase(pFile, null)); } @Override public InputStream getInputStream(final ITileSource pTileSource, final MapTile pTile) { try { InputStream ret = null; final String[] tile = {"tile"}; final long x = (long) pTile.getX(); final long y = (long) pTile.getY(); final long z = (long) pTile.getZoomLevel(); final long index = ((z << z) + x << z) + y; final Cursor cur = mDatabase.query("tiles", tile, "key = " + index + " and provider = '" + pTileSource.name() + "'", null, null, null, null); if(cur.getCount() != 0) { cur.moveToFirst(); ret = new ByteArrayInputStream(cur.getBlob(0)); } cur.close(); if(ret != null) { return ret; } } catch(final Throwable e) { logger.warn("Error getting db stream: " + pTile, e); } return null; } @Override public String toString() { return "DatabaseFileArchive [mDatabase=" + mDatabase.getPath() + "]"; } }
格式为mbtiles的数据结构:
// TABLE tiles (zoom_level INTEGER, tile_column INTEGER, tile_row INTEGER, tile_data BLOB); public final static String TABLE_TILES = "tiles"; public final static String COL_TILES_ZOOM_LEVEL = "zoom_level"; public final static String COL_TILES_TILE_COLUMN = "tile_column"; public final static String COL_TILES_TILE_ROW = "tile_row"; public final static String COL_TILES_TILE_DATA = "tile_data";
具体实现:
package org.osmdroid.tileprovider.modules; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.tilesource.ITileSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; public class MBTilesFileArchive implements IArchiveFile { private static final Logger logger = LoggerFactory.getLogger(MBTilesFileArchive.class); private final SQLiteDatabase mDatabase; // TABLE tiles (zoom_level INTEGER, tile_column INTEGER, tile_row INTEGER, tile_data BLOB); public final static String TABLE_TILES = "tiles"; public final static String COL_TILES_ZOOM_LEVEL = "zoom_level"; public final static String COL_TILES_TILE_COLUMN = "tile_column"; public final static String COL_TILES_TILE_ROW = "tile_row"; public final static String COL_TILES_TILE_DATA = "tile_data"; private MBTilesFileArchive(final SQLiteDatabase pDatabase) { mDatabase = pDatabase; } public static MBTilesFileArchive getDatabaseFileArchive(final File pFile) throws SQLiteException { return new MBTilesFileArchive( SQLiteDatabase.openDatabase( pFile.getAbsolutePath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.OPEN_READONLY)); } @Override public InputStream getInputStream(final ITileSource pTileSource, final MapTile pTile) { try { InputStream ret = null; final String[] tile = { COL_TILES_TILE_DATA }; final String[] xyz = { Integer.toString(pTile.getX()) , Double.toString(Math.pow(2, pTile.getZoomLevel()) - pTile.getY() - 1) // Use Google Tiling Spec , Integer.toString(pTile.getZoomLevel()) }; final Cursor cur = mDatabase.query(TABLE_TILES, tile, "tile_column=? and tile_row=? and zoom_level=?", xyz, null, null, null); if(cur.getCount() != 0) { cur.moveToFirst(); ret = new ByteArrayInputStream(cur.getBlob(0)); } cur.close(); if(ret != null) { return ret; } } catch(final Throwable e) { logger.warn("Error getting db stream: " + pTile, e); } return null; } @Override public String toString() { return "DatabaseFileArchive [mDatabase=" + mDatabase.getPath() + "]"; } }
GEMF文件格式的:
GEMF格式的文件存储算法比较复杂,但是据说为加载最快的方式。
package org.osmdroid.tileprovider.modules; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.tilesource.ITileSource; import org.osmdroid.util.GEMFFile; public class GEMFFileArchive implements IArchiveFile { private final GEMFFile mFile; private GEMFFileArchive(final File pFile) throws FileNotFoundException, IOException { mFile = new GEMFFile(pFile); } public static GEMFFileArchive getGEMFFileArchive(final File pFile) throws FileNotFoundException, IOException { return new GEMFFileArchive(pFile); } @Override public InputStream getInputStream(final ITileSource pTileSource, final MapTile pTile) { return mFile.getInputStream(pTile.getX(), pTile.getY(), pTile.getZoomLevel()); } @Override public String toString() { return "GEMFFileArchive [mGEMFFile=" + mFile.getName() + "]"; } }
ZIP格式:
package org.osmdroid.tileprovider.modules; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.tilesource.ITileSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ZipFileArchive implements IArchiveFile { private static final Logger logger = LoggerFactory.getLogger(ZipFileArchive.class); private final ZipFile mZipFile; private ZipFileArchive(final ZipFile pZipFile) { mZipFile = pZipFile; } public static ZipFileArchive getZipFileArchive(final File pFile) throws ZipException, IOException { return new ZipFileArchive(new ZipFile(pFile)); } @Override public InputStream getInputStream(final ITileSource pTileSource, final MapTile pTile) { final String path = pTileSource.getTileRelativeFilenameString(pTile); try { final ZipEntry entry = mZipFile.getEntry(path); if (entry != null) { return mZipFile.getInputStream(entry); } } catch (final IOException e) { logger.warn("Error getting zip stream: " + pTile, e); } return null; } @Override public String toString() { return "ZipFileArchive [mZipFile=" + mZipFile.getName() + "]"; } }
实现:最初显示”中国地图(省界)“,放大到一定程度显示”中国地图(地市分界)“,继续放大切换到Google街道地图。
<!DOCTYPE html> <html> <head> <title>中国地图</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link rel="stylesheet" href="css/style.css" type="text/css" /> <style type="text/css" media="screen"> body { margin:0; padding:0; height:100%; } .bigmap { position:absolute; left:0; top:0px; padding:0; width:100%; height:100%; border:1px solid #333; } </style> <script type="text/javascript" src="js/OpenLayers/lib/OpenLayers.js"></script> <script src="http://maps.google.com/maps/api/js?v=3.6&sensor=false"></script> <script type="text/javascript"> var map, layer_province, layer_city, layer_street, vlayer; // 第一次打开地图的中心位置(经度、纬度) var firstLon = 109.33981; var firstLat = 33.72419; /* * 切换地图比例尺设置 * scale1: * scale2: 省地图和地市地图切换点 * scale3: 地市地图和谷歌街道地图切换点 */ var scale1 = 20000000; var scale2 = 10000000; var scale3 = 2000000; var level1 = 1; var level2 = 2; var level3 = 3; var level = level1; function init() { // 创建MAP DIV框架 map = new OpenLayers.Map("map", { maxResolution: 'auto', maxExtent: new OpenLayers.Bounds( 73.44696044921875, 6.318641185760498, 135.08583068847656, 53.557926177978516), displayProjection: new OpenLayers.Projection("EPSG:4326") }); // 增加中国地图(省界) Layer addProvinceLayer() // 增加中国地图(地市分界) Layer addCityLayer(); // 增加Google Street Layer addStreetLayer(); // 增加地图Layer切换Register addMapRegister(); map.zoomToMaxExtent(); map.setCenter(new OpenLayers.LonLat(firstLon, firstLat), 0); map.addControl(new OpenLayers.Control.Scale()); map.addControl(new OpenLayers.Control.MousePosition()); map.addControl(new OpenLayers.Control.LayerSwitcher()); } function addProvinceLayer() { layer_province = new OpenLayers.Layer.WMS( "China:province", "http://10.0.0.239:8081/geoserver/wms", { layers: "China:province", }, { singleTile: true, //set single label isBaseLayer: true, projection: "EPSG:4326", maxExtent: new OpenLayers.Bounds( 73.44696044921875, 6.318641185760498, 135.08583068847656, 53.557926177978516) } ); map.addLayer(layer_province); } function addCityLayer() { layer_city = new OpenLayers.Layer.WMS( "China:city", "http://10.0.0.239:8081/geoserver/wms", { layers: "China:city" }, { singleTile: true, //set single label visibility: false, projection: "EPSG:4326", displayInLayerSwitcher: false, maxExtent: new OpenLayers.Bounds( 73.44696044921875, 6.318641185760498, 135.08583068847656, 53.557926177978516) } ); map.addLayer(layer_city); } function addStreetLayer() { layer_street = new OpenLayers.Layer.Google( "Google Streets", // the default { numZoomLevels: 18 , projection: "EPSG:900913", visibility: false, displayInLayerSwitcher: false, maxExtent: new OpenLayers.Bounds( -128 * 156543.03390625, -128 * 156543.03390625, 128 * 156543.03390625, 128 * 156543.03390625) } ); map.addLayer(layer_street); } function addMapRegister(){ map.events.register("zoomend", map, function(){ var cur_scale = map.getScale(); if(cur_scale >= scale2) { if(level != level1) { if(level == level2) { map.setCenter(map.center.transform( new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:4326") ), map.getZoom()); } else { map.setCenter(map.center.transform( new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326") ), map.getZoom()); } level = level1; layer_province.displayInLayerSwitcher = true; layer_city.displayInLayerSwitcher = false; layer_street.displayInLayerSwitcher = false; map.setBaseLayer(layer_province); layer_city.setVisibility(false); layer_street.setVisibility(false); } } else if(cur_scale < scale2 && cur_scale >= scale3){ if(level != level2){ if(level == level1) { map.setCenter(map.center.transform( new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:4326") ), map.getZoom()); } else { map.setCenter(map.center.transform( new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326") ), map.getZoom()); } level = level2; layer_province.displayInLayerSwitcher = false; layer_city.displayInLayerSwitcher = true; layer_street.displayInLayerSwitcher = false; map.setBaseLayer(layer_city); layer_province.setVisibility(false); layer_street.setVisibility(false); } } else if(cur_scale < scale3){ if(level != level3) { map.setCenter(map.center.transform( new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913") ), map.getZoom()); level = level3; layer_province.displayInLayerSwitcher = false; layer_city.displayInLayerSwitcher = false; layer_street.displayInLayerSwitcher = true; map.setBaseLayer(layer_street); layer_city.setVisibility(false); layer_province.setVisibility(false); } } }); } </script> </head> <body onload="init()" > <div id="map" class="bigmap"></div> </body> </html>
参考: