Gis底图的缓存一般都是使用切片,不是把切片放在本地直接读取就是利用切片生成tpk或者mmpk文件读取,今天就讲一下使用.MBTiles/.db(都是sqlite的数据库,MBTiles其实就是sqlite3的数据库,是给移动平台离线存储用的)。
官方文档地址:https://developers.arcgis.com/android/latest/guide/tasks-and-jobs.htm
如果你的地图是发布在ArcGIS Server上,也可以看这里客户端使用地图缓存的方法
SQLite数据库实现缓存
1、数据库文件
0-14,表示地图包括的图层
2、读取文件
可以把文件放到APK安装包中,也可以放在服务器上让用户下载
public class MyUtils {
public final static String File_name = "shijiazhuan_Road-0-14.db";
public final static String Package_name = "com.cnbs.cableinspection"; //项目包路径
public final static String Save_Path = "/data"
+ Environment.getDataDirectory().getAbsolutePath()+"/"
+ Package_name
+"/arcgis";
public static void saveAssetsToSD(Context context) {
try {
String filename = Save_Path + "/" + File_name;
File dir = new File(Save_Path);
if (!dir.exists()) {
dir.mkdir();
}
if (!(new File(filename)).exists()) {
InputStream is = context.getResources().getAssets().open(File_name);//assets目录下资源文件名
FileOutputStream fos = new FileOutputStream(filename);
byte[] buffer = new byte[1024];
int count = 0;
while ((count = is.read(buffer)) > 0) {
fos.write(buffer, 0, count);
}
fos.close();
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
...
}
在Application
中把db文件写入手机内存
MyUtils.saveAssetsToSD(this);
3、重写切片加载图层
public class DBTiledLayer extends ImageTiledLayer {
private SQLiteDatabase mapDb;
private int mLevels = 0;
public static DBTiledLayer init(String path) {
SQLiteDatabase mapDb;
int mLevels = 0;
try {
mapDb = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
} catch (SQLException ex) {
Log.e("MBTiles", ex.getMessage());
throw (ex);
}
// Default TMS bounds = bounds of Web Mercator projection
Envelope envWGS = new Envelope(-180.0, -85.0511, 180.0, 85.0511, SpatialReferences.getWgs84());
// See if the MBTiles DB defines their own Bounds in the metadata table
Cursor bounds = mapDb.rawQuery("SELECT value FROM metadata WHERE name = 'bounds'", null);
if (bounds.moveToFirst()) {
String bs = bounds.getString(0);
String[] ba = bs.split(",", 4);
if (ba.length == 4) {
double leftLon = Double.parseDouble(ba[0]);
double topLat = Double.parseDouble(ba[3]);
double rightLon = Double.parseDouble(ba[2]);
double bottomLat = Double.parseDouble(ba[1]);
envWGS = new Envelope(leftLon, bottomLat, rightLon, topLat, SpatialReferences.getWgs84());
}
}
Envelope envWeb = (Envelope) GeometryEngine.project(envWGS,
SpatialReferences.getWebMercator());
Point origin = new Point(envWeb.getXMin(), envWeb.getYMax(), envWeb.getSpatialReference());
Cursor maxLevelCur = mapDb.rawQuery("SELECT MAX(zoom_level) AS max_zoom FROM tiles", null);
if (maxLevelCur.moveToFirst()) {
mLevels = maxLevelCur.getInt(0);
}
Log.i("TAG", "Max levels = " + Integer.toString(mLevels));
double[] resolution = new double[mLevels];
double[] scale = new double[mLevels];
List<LevelOfDetail> lod = new ArrayList<>(mLevels);
for (int i = 0; i < mLevels; i++) {
// see the TMS spec for derivation of the level 0 scale and resolution
// For each level the resolution (in meters per pixel) doubles
resolution[i] = 156543.032 / Math.pow(2, i);
// Level 0 scale is 1:554,678,932. Each level doubles this.
scale[i] = 554678932 / Math.pow(2, i);
lod.add(new LevelOfDetail(i, resolution[i], scale[i]));
}
/*
* Note, the constructor must set the following values or we won't send the
* status change events to listeners and the tiles will not be fetched
*
* Origin is Top Left (web Mercator) , the rest are defined by the TMS
* Global-mercator spec (scales, resolution, 96dpi 256x256 pixel tiles) See:
* http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-mercator
*/
TileInfo ti = new TileInfo(96, TileInfo.ImageFormat.PNG, lod, origin, origin.getSpatialReference(), 256, 256);
return new DBTiledLayer(ti, envWeb, mapDb, mLevels);
}
private DBTiledLayer(TileInfo tileInfo, Envelope fullExtent, SQLiteDatabase mapDb, int mLevels) {
super(tileInfo, fullExtent);
this.mapDb = mapDb;
this.mLevels = mLevels;
}
@Override
protected byte[] getTile(TileKey tileKey) {
// need to flip origin
int nRows = (1 << tileKey.getLevel()); // Num rows = 2^level
int tmsRow = nRows - 1 - tileKey.getRow();
Cursor imageCur = mapDb.rawQuery("SELECT tile_data FROM tiles WHERE zoom_level = " + Integer.toString(tileKey.getLevel())
+ " AND tile_column = " + Integer.toString(tileKey.getColumn()) + " AND tile_row = " + Integer.toString(tmsRow), null);
if (imageCur.moveToFirst()) {
return imageCur.getBlob(0);
}
return null; // Alternatively we might return a "no data" tile
}
}
4、离线加载地图
if (isOffline) {
String filename = MyUtils.Save_Path + "/" + MyUtils.File_name;
if (!(new File(filename)).exists())return;//防止闪退
DBTiledLayer tiledLayer = DBTiledLayer.init(filename);
tiledLayer.setMinScale(MinScale); //控制缩小,数值越大,缩小倍数越大,看的范围越广
tiledLayer.setMaxScale(MaxScale); //控制放大,数值越小,放大倍数越高
Basemap basemap = new Basemap(tiledLayer);
mArcGISMap = new ArcGISMap(basemap);
}
ExportTileCacheTask缓存实现
1、看文档
首先我们查看文档ArcGIS Runtime SDK for Android
下的Fundamentals
–> Tasks and jobs
红框内的文字翻译出来大概是:
- 使用GeodatabaseSyncTask下载,收集和更新地理信息
- 使用ExportTileCacheTask下载并显示平铺底图
- 使用LocatorTask查找地址并从地图位置查找地址
- 使用RouteTask计算点对点或多站路线,并获取驾车路线
- 使用GeoprocessingTask执行地理处理模型来执行复杂的GIS分析
任务要么直接从Task
的异步方法返回结果,要么使用jobs
来提供状态更新和结果。其中直接从任务上的异步方法返回结果的有LocatorTask.geocodeAsync
和RouteTask.solveRouteAsync
。 对于更复杂或更长时间的运行操作,任务将改为使用作业jobs
。
通过文档我们就知道了缓存底图我们需要的是ExportTileCacheTask
这个类。
那么我们要用到的ExportTileCacheTask
就需要使用ExportTileCacheTask
的job
–ExportTileCacheJob
来提供状态更新和结果了。
2、使用Task
2-1、使用直接返回结果的Task
LocatorTask
、RouteTask
1、通过初始化任务来创建任务以使用所需的数据或服务。
- 一些操作可以在线和离线使用。
2、定义任务输入。
- 一些操作只需要简单的值输入(例如,一个简单的地理编码操作可能只需要一个地址字符串作为输入)。
- 其他需要定义参数(例如,将地理编码操作限制到特定的国家/地区)。
3、调用异步操作方法,传递你定义的输入。
4、根据需要使用操作的结果,例如在地图上显示地理编码结果。
我们这篇文章主要介绍使用ExportTileCacheTask
来缓存底图,LocatorTask
、RouteTask
这些直接返回结果的Task就不做过多介绍了,可以去官方文档查看。
2-2、使用直接返回结果的Task
1、通过初始化任务来创建任务以使用所需的数据或服务。
ExportTileCacheTask cacheTask = new ExportTileCacheTask(mMapUrl);
2、定义任务的输入参数。
public String mMapUrl= "http://119.97.224.2:8399/PBS/rest/services/MapsRoad/MapServer"; //街道图
3、调用异步操作方法获取作业,并传入您定义的输入参数。
ExportTileCacheJob cacheJob = cacheTask.exportTileCacheAsync(cacheParameters, mExportPath + "/" + "test.tpk");
4、开始工作。
cacheJob.start();
5、(可选)监听作业状态的更改并检查作业消息,例如更新UI并向用户报告进度。
cacheJob.addJobChangedListener(new Runnable() {
@Override
public void run() {
List<Job.Message> messages = cacheJob.getMessages();
updateUiWithProgress(messages.get(messages.size() - 1).getMessage());
}
});
6、监听工作完成情况,并从操作中获得结果。检查作业中的错误,如果成功,请使用结果。
cacheJob.addJobDoneListener(new Runnable() {
@Override
public void run() {
if (cacheJob.getError() != null) {
dealWithException(exportJob.getError());
return;
}
if (cacheJob.getStatus() == Job.Status.SUCCEEDED) {
final TileCache result= cacheJob.getResult();
ArcGISTiledLayer tiledLayer = new ArcGISTiledLayer(result);
mapView.getMap().getOperationalLayers().add(tiledLayer);
}
}
});
* 方法完整代码*
final ExportTileCacheTask cacheTask = new ExportTileCacheTask(mMapUrl);
ExportTileCacheParameters cacheParameters = new ExportTileCacheParameters();
cacheParameters.getLevelIDs().addAll(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
//计算中心点
int measuredWidth = mMapView.getMeasuredWidth();
int measuredHeight = mMapView.getMeasuredHeight();
android.graphics.Point point = new android.graphics.Point(measuredWidth / 2, measuredHeight / 2);
Point centerDot = mMapView.screenToLocation(point);
cacheParameters.setAreaOfInterest(new Envelope(centerDot,measuredWidth*5,measuredHeight*5));
String date = MyDateTimeUtils.hasNowDate();
final ExportTileCacheJob cacheJob = cacheTask.exportTileCacheAsync(cacheParameters, mExportPath + "/" + date + ".tpk");
cacheJob.addJobChangedListener(new Runnable() {
@Override
public void run() {
List<Job.Message> messages = cacheJob.getMessages();
// updateUiWithProgress(messages.get(messages.size() - 1).getMessage());
}
});
cacheJob.addJobDoneListener(new Runnable() {
@Override
public void run() {
if (cacheJob.getError() != null) {
ArcGISRuntimeException error =cacheJob.getError();
// return;
}
if (cacheJob.getStatus() == Job.Status.SUCCEEDED) {
final TileCache exportedTileCache = cacheJob.getResult();
ArcGISTiledLayer tiledLayer = new ArcGISTiledLayer(exportedTileCache);
mMapView.getMap().getOperationalLayers().add(tiledLayer);
}
}
});
cacheJob.start();
2-3、示例代码
下载官方demo,运行这个Module
参考文章:
1、Android开发,arcgis自定义layer-历史影像和地图缓存的实现
2、arcgis for android 本地缓存