专栏首页C++核心准则原文翻译自学HarmonyOS应用开发(52)- 地图数据拼接和缓存

自学HarmonyOS应用开发(52)- 地图数据拼接和缓存

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

瓦片数据类

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

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

代码很简单,唯一的一个小技巧是根据缩放倍数和瓦片位置计算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();
                    }
                });
            }
        }
    });
}

显示地图数据

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

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

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

参考代码

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

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

本文分享自微信公众号 - 面向对象思考(OOThinkingDalian)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-06-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 自学HarmonyOS应用开发(50)- 获取瓦片地图数据

    读者可以参照文后的链接了解瓦片地图的原理,这里不再重复。简单地说,瓦片地图就是将地图根据缩放层级进行分割,例如缩放层级为3,就表明将地图分为2的3次方行和2的3...

    面向对象思考
  • 自学HarmonyOS应用开发(51)- 获取瓦片地图数据(续)

    以下是获取瓦片数据的代码。由于整个获取过程的时间无法确定,因此将获取动作放入另外的任务。获取任务结束之后再触发UI线程的更新动作。

    面向对象思考
  • 自学HarmonyOS应用开发(55)- 使用对象关系映射数据库保存地图数据

    前一篇文章实现了地图数据的正确表示,但是由于每次执行都需要至少一次从网上获取地图数据,不可避免地产生显示延迟。本文介绍利用对象数据库储存已经获取的地图数据,从而...

    面向对象思考
  • 自学HarmonyOS应用开发(61)- 使用异步派发任务后台更新地图数据

    当第一次表示某地的地图数据时,由于数据需要从网络下载,因此会造成初次表示时间过长而影响响应速度的问题。我们使用异步派发任务解决这个问题。先看显示效果:

    面向对象思考
  • HarmonyOS简介

    前两天,华为发布了HarmonyOS 2.0,俺也赶个时髦,给大家简单介绍下HarmonyOS。

    xiangzhihong
  • HarmonyOS与Android的全面对比

    第二是我个人非常看好鸿蒙系统的未来,清楚明白华为和一些民族企业担负的责任和国人的期待,虽然带着一些民族感情;鸿蒙刚发布的时候自己是非常激动的,但是后来项目太忙一...

    肉眼品世界
  • 技术分析 | HarmonyOS到底是不是Android套皮?

    最近鸿蒙系统关注度好高,支持与反对、看好和看衰、「自主的全场景分布式系统」和「Android套壳」各执一词,吵的不可开交。

    刘盼
  • 自学鸿蒙应用开发(22)- 在应用本地存储少量数据

    鸿蒙系统中的轻量级偏好数据库,主要用于保存应用的一些常用配置。数据存储在本地文件中,同时也加载在内存中的,所以访问速度更快,效率更高。

    面向对象思考
  • 十问华为HarmonyOS:开源一个月,开发者生态建设进度如何?

    基础软件的开源面临着非常大的挑战,除了技术研发困难重重,开源生态和社区的建设更是难上加难。9 月 10 日,在华为开发者大会 2020 上,华为消费者业务 CE...

    深度学习与Python
  • 【第22期】HarmonyOS应用开发(基础篇)

    这不就是说,以后华为手机都是鸿蒙系统了嘛?鸿蒙还发出了一条视频,视频中显示2021年6月2号将开启鸿蒙操作系统及华为全场景新品发布会。预计现在支持EMUI11升...

    siberiawolf
  • 自学HarmonyOS应用开发(62)- 使用对象关系映射数据库保存设定信息

    除了地图数据,秒表应用还有一些其他希望保存的数据,例如上次定位的位置,地图画面的缩放比例等。本文介绍通过对象关系映射数据库技术保存这些信息的方法。

    面向对象思考
  • 抢先学鸿蒙(HarmonyOS)2.0,你就是下一个大咖!

    2020年9月10日,华为开发者大会发布了鸿蒙(HarmonyOS)2.0。HarmonyOS是全场景操作系统。也就是说,从理论上,HarmonyOS可以在任何...

    蒙娜丽宁
  • 程序员看华为HarmonyOS首发

    HarmonyOS代码正式开源,9月10日下午朋友圈散布着这条消息,科技圈炸锅了。各种声音的都有,我也挺好奇的,目前Android、iOS一统江湖,Harmon...

    马上就说
  • 牛掰了!鸿蒙与Android完美融合,将鸿蒙设备当Android设备用

    1. 你看着是鸿蒙,其实它是Android,你看着是Android,其实它是鸿蒙

    蒙娜丽宁
  • 送你一份最新的前端周报

    Vue 3.2 正式发布,<script setup> + TS + Volar = 真香

    落落落洛克
  • HarmonyOS-对Android开发者也太友好了吧

    2020年9月10日,华为消费者业务软件部总裁王成录又一次站在了松山湖华为开发者大会的主舞台上。今年,他带来了万众瞩目的华为鸿蒙HarmonyOS2.0...

    Android扫地僧
  • 安装体验鸿蒙Harmony OS开发工具HUAWEI DevEco Studio 2.1和汉化

    华为鸿蒙Harmony OS,提供在不同设备之间可分可合可流转的原子化服务能力,可轻松调用设备组合中不同硬件的能力,升级全场景服务体验。根据官网介绍https:...

    TX-QGS
  • 【HarmonyOS 专题】01 基础 Mac 环境安装配置

    HarmonyOS 已于 2020 年 12 更新到 2.0 版本;和尚周围的人都在学习和研究,和尚也想学习一下;今天和尚从 0 开始学习,第一步简单介绍基础...

    阿策小和尚
  • 展现鸿蒙的独特魅力:跨设备调用窗口(Page Ability)

    HarmonyOS的核心特性(或称为卖点)之一就是软总线技术,而Page Ability的跨设备迁移是软总线的一个具体技术实现。所谓跨设备迁移Page Abil...

    蒙娜丽宁

扫码关注云+社区

领取腾讯云代金券