GIS地圖學習筆記五之底圖的快取
Gis底圖的快取一般都是使用切片,不是把切片放在本地直接讀取就是利用切片生成tpk或者mmpk檔案讀取,今天就講一下使用.MBTiles/.db(都是sqlite的資料庫,MBTiles其實就是sqlite3的資料庫,是給移動平臺離線儲存用的)。
如果你的地圖是釋出在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