GIS地圖學習筆記五之底圖的緩存

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.geocodeAsyncRouteTask.solveRouteAsync。 對於更復雜或更長時間的運行操作,任務將改爲使用作業jobs

通過文檔我們就知道了緩存底圖我們需要的是ExportTileCacheTask這個類。

那麼我們要用到的ExportTileCacheTask就需要使用ExportTileCacheTaskjobExportTileCacheJob來提供狀態更新和結果了。


2、使用Task

2-1、使用直接返回結果的Task

LocatorTaskRouteTask

1、通過初始化任務來創建任務以使用所需的數據或服務。

  • 一些操作可以在線和離線使用。

2、定義任務輸入。

  • 一些操作只需要簡單的值輸入(例如,一個簡單的地理編碼操作可能只需要一個地址字符串作爲輸入)。
  • 其他需要定義參數(例如,將地理編碼操作限制到特定的國家/地區)。

3、調用異步操作方法,傳遞你定義的輸入。
4、根據需要使用操作的結果,例如在地圖上顯示地理編碼結果。

我們這篇文章主要介紹使用ExportTileCacheTask來緩存底圖,LocatorTaskRouteTask這些直接返回結果的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 本地緩存


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章