1. 程式人生 > >GIS地圖學習筆記五之底圖的快取

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.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

這裡寫圖片描述