上一篇文章中我们获取了当前位置所处的地图瓦片并表示,本文介绍获取更多的瓦片数据并进行拼接的方法。动作演示视频如下:
瓦片数据类
我们假设显示区域的中心是当前位置,以这个位置为中心分别向上下左右扩展地图瓦片就可以铺满整个表示区域的地图数据。为了方便管理,我们设计了瓦片数据类:
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加油站
猜你喜欢