自學(xué)HarmonyOS應(yīng)用開(kāi)發(fā)(52)- 地圖數(shù)據(jù)拼接和緩存
上一篇文章中我們獲取了當(dāng)前位置所處的地圖瓦片并表示,本文介紹獲取更多的瓦片數(shù)據(jù)并進(jìn)行拼接的方法。動(dòng)作演示視頻如下:
瓦片數(shù)據(jù)類
我們假設(shè)顯示區(qū)域的中心是當(dāng)前位置,以這個(gè)位置為中心分別向上下左右擴(kuò)展地圖瓦片就可以鋪滿整個(gè)表示區(qū)域的地圖數(shù)據(jù)。為了方便管理,我們?cè)O(shè)計(jì)了瓦片數(shù)據(jù)類:
public class Tile extends PixelMapHolder {static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00208, "Tile");int x = 0;int y = 0;int z = 0;// 地圖來(lái)源public enum MapSource {GAODE_VECTOR, GAODE_ROAD, GAODE_SATELLITE}public Tile(PixelMap pixelMap) {super(pixelMap);}public void setTileInfo(int tile_x, int tile_y, int zoom) {x = tile_x;y = tile_y;z = zoom;}public static Tile createTile(MapSource src, int tile_x, int tile_y, int zoom){String urlString = String.format(getMapUrlString(src), tile_x, tile_y, zoom);PixelMap map = Tile.getImagePixmap(urlString);if(map != null) {Tile tile = new Tile(map);tile.setTileInfo(tile_x, tile_y, zoom);return tile;}else {//HiLog.info(LABEL,"createTile Fail: zoom=%{public}d,row=%{public}d,col=%{public}d", zoom, tile_y, tile_x);return null;}}public Size calculateOffset(double longitude, double latitude){//獲取位圖尺寸Size imageSize = getPixelMap().getImageInfo().size;//獲取當(dāng)前坐標(biāo)所處瓦片位置int tile_x = getTileX(longitude, z);int tile_y = getTileY(latitude, z);//計(jì)算瓦片經(jīng)度范圍double long_from = getTileLongitude(tile_x, z);double long_to = getTileLongitude(tile_x + 1, z);//計(jì)算玩片緯度范圍double lat_from = getTileLatitude(tile_y, z);double lat_to = getTileLatitude(tile_y + 1, z);//計(jì)算Tile內(nèi)偏移量int offset_x = (int)((longitude - long_from) / (long_to - long_from) * (imageSize.width));int offset_y = (int)((latitude - lat_from) / (lat_to - lat_from) * (imageSize.height));offset_x -= (x - tile_x) * imageSize.width;offset_y -= (y - tile_y) * imageSize.height;//HiLog.info(LABEL,"calculateOffset: x=%{public}d,y=%{public}d,offset_x=%{public}d,offset_y=%{public}d", x, y, offset_x, offset_y);//HiLog.info(LABEL,"calculateOffset: x=%{public}d,y=%{public}d", x, y);return new Size(offset_x, offset_y);}public static String getMapUrlString(MapSource src){// 高德地圖 - 矢量final String GAODE_V_MAP_URL = "https://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=2&style=8&x=%d&y=%d&z=%d";// 高德地圖 - 道路final String GAODE_R_MAP_URL = "https://webst02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=2&style=8&x=%d&y=%d&z=%d";// 高德地圖 - 衛(wèi)星final String GAODE_S_MAP_URL = "https://webst01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=2&style=6&x=%d&y=%d&z=%d";switch(src){case GAODE_VECTOR:return GAODE_V_MAP_URL;case GAODE_ROAD:return GAODE_R_MAP_URL;case GAODE_SATELLITE:return GAODE_S_MAP_URL;default:return null;}}//https://wiki.openstreetmap.org/wiki/Slippy_map_tilenamesstatic int getTileX(double long_deg, int zoom){int total_cols = (int)Math.pow(2, zoom);return (int)((long_deg + 180)/360 * total_cols);}static int getTileY(double lat_deg, int zoom){double tan = Math.tan(Math.toRadians(lat_deg));double asinh = Math.log(tan + Math.sqrt(tan * tan + 1));return (int)((1.0 - asinh / Math.PI) * Math.pow(2, zoom - 1));}static double getTileLongitude(int tile_x, int zoom){return tile_x / Math.pow(2, zoom) * 360 - 180;}static double getTileLatitude(int tile_y, int zoom){return Math.toDegrees(Math.atan(Math.sinh(Math.PI * (1 - 2 * tile_y / Math.pow(2, zoom)))));}/*** 獲取網(wǎng)絡(luò)中的ImagePixmap* @param urlString* @return*/static PixelMap getImagePixmap(String urlString) {try {URL url = new URL(urlString);URLConnection con = url.openConnection();con.setConnectTimeout(500*1000);InputStream is = con.getInputStream();ImageSource source = ImageSource.create(is, new ImageSource.SourceOptions());ImageSource.DecodingOptions options = new ImageSource.DecodingOptions();options.desiredSize = new Size(512,512);PixelMap pixelMap = source.createPixelmap(options);is.close();return pixelMap;} catch (Exception e) {return null;}}}
函數(shù)calculateOffset用于計(jì)算當(dāng)前瓦片和畫面中心之間的偏移量;getMapUrlString是一個(gè)工廠方法用于根據(jù)地圖數(shù)據(jù)源,瓦片位置和當(dāng)前的縮放級(jí)別生成瓦片數(shù)據(jù)。
瓦片數(shù)據(jù)緩存
如果每次都重新獲取地圖數(shù)據(jù)勢(shì)必拖慢表示速度,因此準(zhǔn)備了一個(gè)瓦片數(shù)據(jù)緩存類,用來(lái)保存已經(jīng)獲取的地圖數(shù)據(jù):
public class TileMapData {static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00207, "TileMapData");Map<Long, Tile> mapData = new HashMap<Long, Tile>();void setData(int zoom, int tile_x, int tile_y, Tile tile){//HiLog.info(LABEL, "TileMapData.setData!");mapData.put(getKey(zoom, tile_x, tile_y), tile);}Tile getData(int zoom, int tile_x, int tile_y){//HiLog.info(LABEL, "TileMapData.getData!");Tile tile = mapData.get(getKey(zoom, tile_x, tile_y));return tile;}void clear(){mapData.clear();}private Long getKey(int zoom, int tile_x, int tile_y){return new Long((zoom << 50) + (tile_x << 20) + tile_y);}}
代碼很簡(jiǎn)單,唯一的一個(gè)小技巧是根據(jù)縮放倍數(shù)和瓦片位置計(jì)算key值。
獲取瓦片數(shù)據(jù)
下面是通過(guò)x,y兩個(gè)方向循環(huán)獲取足以覆蓋整個(gè)表示區(qū)域的瓦片數(shù)據(jù)的代碼。如果需要的數(shù)據(jù)已經(jīng)存在則不再重新獲取;如果存在新獲取的地圖數(shù)據(jù),則觸發(fā)畫面更新。
public void loadMapTile(boolean invalidate){getContext().getGlobalTaskDispatcher(TaskPriority.DEFAULT).asyncDispatch(new Runnable() {public void run() {HiLog.info(LABEL, "TileMap.loadMapTile.run!");int tileCol = Tile.getTileX(longitude, zoom);int tileRow = Tile.getTileY(latitude, zoom);boolean need_update = false;for(int col = tileCol - 1; col <= tileCol + 1; col++) {for (int row = tileRow - 1; row <= tileRow + 1; row++) {Tile tile = mapData.getData(zoom, col, row);if (tile == null) {//HiLog.info(LABEL,"loadMapTile: zoom=%{public}d,row=%{public}d,col=%{public}d", zoom, row, col);tile = Tile.createTile(mapSource, col, row, zoom);if(tile != null) {//HiLog.info(LABEL,"createTile Succefully!: zoom=%{public}d,row=%{public}d,col=%{public}d", zoom, row, col);mapData.setData(zoom, col, row, tile);need_update = true;}}}}if(need_update || invalidate){getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {public void run() {//HiLog.info(LABEL, "TileMap.loadMapTile.run.TileMap.this.invalidate!");TileMap.this.invalidate();}});}}});}
顯示地圖數(shù)據(jù)
以下是顯示地圖數(shù)據(jù)的代碼:
public void onDraw(Component component, Canvas canvas) {HiLog.info(LABEL, "TileMap.onDraw!");int tileCol = Tile.getTileX(longitude, zoom);int tileRow = Tile.getTileY(latitude, zoom);boolean need_load = false;for(int col = tileCol - 1; col <= tileCol + 1; col++){for(int row = tileRow - 1; row <= tileRow + 1; row++){Tile tile = mapData.getData(zoom, col, row);if(tile != null) {Size imageSize = tile.getPixelMap().getImageInfo().size;Size offset = tile.calculateOffset(longitude, latitude);canvas.drawPixelMapHolder(tile,getWidth() / 2 - offset.width,getHeight() / 2 - offset.height,new Paint());}else{need_load = true;}}}if(need_load){loadMapTile(false);}}
如果存在沒(méi)有準(zhǔn)備好的數(shù)據(jù),則觸發(fā)一次地圖數(shù)據(jù)獲取處理。
參考代碼
完整代碼可以從以下鏈接下載:
https://github.com/xueweiguo/Harmony/tree/master/StopWatch
參考資料
Slippy map tilenames(包含各種轉(zhuǎn)換示例代碼):
https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames\
董昱老師的TinyMap:
https://gitee.com/dongyu1009/tiny-map-for-harmony-os/tree/master/tinymap
作者著作介紹
《實(shí)戰(zhàn)Python設(shè)計(jì)模式》是作者去年3月份出版的技術(shù)書籍,該書利用Python 的標(biāo)準(zhǔn)GUI 工具包tkinter,通過(guò)可執(zhí)行的示例對(duì)23 個(gè)設(shè)計(jì)模式逐個(gè)進(jìn)行說(shuō)明。這樣一方面可以使讀者了解真實(shí)的軟件開(kāi)發(fā)工作中每個(gè)設(shè)計(jì)模式的運(yùn)用場(chǎng)景和想要解決的問(wèn)題;另一方面通過(guò)對(duì)這些問(wèn)題的解決過(guò)程進(jìn)行說(shuō)明,讓讀者明白在編寫代碼時(shí)如何判斷使用設(shè)計(jì)模式的利弊,并合理運(yùn)用設(shè)計(jì)模式。

對(duì)設(shè)計(jì)模式感興趣而且希望隨學(xué)隨用的讀者通過(guò)本書可以快速跨越從理解到運(yùn)用的門檻;希望學(xué)習(xí)Python GUI 編程的讀者可以將本書中的示例作為設(shè)計(jì)和開(kāi)發(fā)的參考;使用Python 語(yǔ)言進(jìn)行圖像分析、數(shù)據(jù)處理工作的讀者可以直接以本書中的示例為基礎(chǔ),迅速構(gòu)建自己的系統(tǒng)架構(gòu)。
覺(jué)得本文有幫助?請(qǐng)分享給更多人。
關(guān)注微信公眾號(hào)【面向?qū)ο笏伎肌枯p松學(xué)習(xí)每一天!
面向?qū)ο箝_(kāi)發(fā),面向?qū)ο笏伎迹?/span>
