花粉乐分享平台宣传视频
> 华为资讯 > 华为资讯 > 鸿蒙编程 > 自学HarmonyOS应用开发(52)- 地图数据拼接和缓存
自学HarmonyOS应用开发(52)- 地图数据拼接和缓存
来源:面向对象思考
2023-01-03 11:08:47
642
管理

上一篇文章中我们获取了当前位置所处的地图瓦片并表示,本文介绍获取更多的瓦片数据并进行拼接的方法。动作演示视频如下:

瓦片数据类

我们假设显示区域的中心是当前位置,以这个位置为中心分别向上下左右扩展地图瓦片就可以铺满整个表示区域的地图数据。为了方便管理,我们设计了瓦片数据类:

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; // 地图来源 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; //获取当前坐标所处瓦片位置 int tile_x = getTileX(longitude, z); int tile_y = getTileY(latitude, z); //计算瓦片经度范围 double long_from = getTileLongitude(tile_x, z); double long_to = getTileLongitude(tile_x 1, z); //计算玩片纬度范围 double lat_from = getTileLatitude(tile_y, z); double lat_to = getTileLatitude(tile_y 1, z); //计算Tile内偏移量 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"; // 高德地图 - 卫星 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_tilenames static 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))))); } /** * 获取网络中的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; } }}

函数calculateOffset用于计算当前瓦片和画面中心之间的偏移量;getMapUrlString是一个工厂方法用于根据地图数据源,瓦片位置和当前的缩放级别生成瓦片数据。

瓦片数据缓存

如果每次都重新获取地图数据势必拖慢表示速度,因此准备了一个瓦片数据缓存类,用来保存已经获取的地图数据:

public class TileMapData { static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00207, "TileMapData"); Map mapData = new HashMap(); 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); }}

代码很简单,唯一的一个小技巧是根据缩放倍数和瓦片位置计算key值。

获取瓦片数据

下面是通过x,y两个方向循环获取足以覆盖整个表示区域的瓦片数据的代码。如果需要的数据已经存在则不再重新获取;如果存在新获取的地图数据,则触发画面更新。

public void loadMapTile(boolean invalidate){ getContext().getGlobalTaskDispatcher(TaskPriority.DEFAULT).asyncDispatch(new Runnable() { @Override 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() { @Override public void run() { //HiLog.info(LABEL, "TileMap.loadMapTile.run.TileMap.this.invalidate!"); TileMap.this.invalidate(); } }); } } });}

显示地图数据

以下是显示地图数据的代码:

@Overridepublic 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); }}

如果存在没有准备好的数据,则触发一次地图数据获取处理。

参考代码

完整代码可以从以下链接下载:

https://github.com/xueweiguo/Harmony/tree/master/StopWatch

参考资料

Slippy map tilenames(包含各种转换示例代码):

https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames

董昱老师的TinyMap:

https://gitee.com/dongyu1009/tiny-map-for-harmony-os/tree/master/tinymap

作者著作介绍

《实战Python设计模式》是作者去年3月份出版的技术书籍,该书利用Python 的标准GUI 工具包tkinter,通过可执行的示例对23 个设计模式逐个进行说明。这样一方面可以使读者了解真实的软件开发工作中每个设计模式的运用场景和想要解决的问题;另一方面通过对这些问题的解决过程进行说明,让读者明白在编写代码时如何判断使用设计模式的利弊,并合理运用设计模式。

对设计模式感兴趣而且希望随学随用的读者通过本书可以快速跨越从理解到运用的门槛;希望学习Python GUI 编程的读者可以将本书中的示例作为设计和开发的参考;使用Python 语言进行图像分析、数据处理工作的读者可以直接以本书中的示例为基础,迅速构建自己的系统架构。

觉得本文有帮助?请分享给更多人。

关注微信公众号【面向对象思考】轻松学习每一天!

面向对象开发,面向对象思考!

花粉社群VIP加油站

2
点赞
赏礼
赏钱
0
收藏
免责声明:本文仅代表作者个人观点,与花粉乐分享无关。其原创性以及文中陈述文字和内容未经本网证实,对本文以及其中全部或者 部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
凡本网注明 “来源:XXX(非花粉乐分享)”的作品,均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对 其真实性负责。
如因作品内容、版权和其它问题需要同本网联系的,请在一周内进行,以便我们及时处理。
QQ:2443165046 邮箱:info@hflfx.com
关于作者
underneath(花粉渠道)
文章
585
主题
0
关注
0
粉丝
0
点击领取今天的签到奖励!
签到排行
随手拍
54个圈友 0个话题
华为手机随手拍,记录生活点滴之美好
华为P30pro
51个圈友 0个话题
这里是华为P30pro手机交流圈,欢迎华为P30pro用户进群交流
体验官
60个圈友 2个话题
华为花粉体验官,体验官专属的交流群
登录后查看您创建的圈子
登录后查看您创建的圈子
所有圈子
杭州互联网违法和不良信息举报平台 网络110报警服务 浙ICP备17046585号
2
0
分享
请选择要切换的马甲:

个人中心

每日签到

我的消息

内容搜索