sunny123456

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

echarts渲染世界地图+中国省份轮廓|中国地图数据文件解码|世界地图文件

近期接到大屏的需求,进入地图时展示世界地图,放大中国区域之后展示中国省份轮廓,然后,就卡住了。 也许大家并不想听我的心灵路程,只想知道答案,所以下面就直接写解决步骤了,但还是希望如果这篇文章帮你解决了问题之后,能回头来这里给个反馈,你们的鼓励是写作者的动力。

这篇文章需要具备的基础:我默认您已经具备初级前端开发的基础和已经成功渲染出来了一个世界地图或中国地图。

解决数据问题

众所周知,echarts有世界地图的数据,也有中国地图的数据,我们只需要将两者结合起来就好了。

中国地图数据地址:import china from "echarts/map/json/china.json"

世界地图数据地址:import world from "echarts/map/json/world.json"

对,就是这么简单,他们都存在于npm包中,当我们找到这个数据时,发现了另一个问题:中国地图是加密的,世界地图是明文的。 解密的代码在这里,关于这个代码是如何得到的,下面会说明。

function decodePolygon(coordinate, encodeOffsets, encodeScale) {
    var result = [];
    var prevX = encodeOffsets[0];
    var prevY = encodeOffsets[1];
    for (var i = 0; i < coordinate.length; i += 2) {
        var x = coordinate.charCodeAt(i) - 64;
        var y = coordinate.charCodeAt(i + 1) - 64;
        // ZigZag decoding
        x = (x >> 1) ^ -(x & 1);
        y = (y >> 1) ^ -(y & 1);
        // Delta deocding
        x += prevX;
        y += prevY;
        prevX = x;
        prevY = y;
        // Dequantize
        result.push([x / encodeScale, y / encodeScale]);
    }
    return result;
}
function decode(json) {
    if (!json.UTF8Encoding) {
        return json;
    }
    var encodeScale = json.UTF8Scale;
    if (encodeScale == null) {
        encodeScale = 1024;
    }
    var features = json.features;
    for (var f = 0; f < features.length; f++) {
        var feature = features[f];
        var geometry = feature.geometry;
        var coordinates = geometry.coordinates;
        var encodeOffsets = geometry.encodeOffsets;
        for (var c = 0; c < coordinates.length; c++) {
            var coordinate = coordinates[c];
            if (geometry.type === "Polygon") {
                coordinates[c] = decodePolygon(
                    coordinate,
                    encodeOffsets[c],
                    encodeScale
                );
            } else if (geometry.type === "MultiPolygon") {
                for (var c2 = 0; c2 < coordinate.length; c2++) {
                    var polygon = coordinate[c2];
                    coordinate[c2] = decodePolygon(
                        polygon,
                        encodeOffsets[c][c2],
                        encodeScale
                    );
                }
            }
        }
    }
    // Has been decoded
    json.UTF8Encoding = false;
    return json;
}

如何使用呢?看这里

let data = decode(china) // 打印这个data,得到就是解密之后的中国地图数据了

如何合并?看这里

import echarts from "echarts";
var chart;
let data = decode(china); // 这里调用的函数就是上面解密的函数
let worldAndChina = Object.assign({}, world); // 原本的world还需要用 所以这里用了一个深拷贝赋值世界地图数据
worldAndChina.features = worldAndChina.features.concat(data.features); // 对,就是这么简单用concat把两者的features合并起来就可以了
chart = echarts.init(this.$refs.chart);
echarts.registerMap("world", worldAndChina);

删除其他国家label

到这里,我们已经成功渲染出了世界地图和中国地图,但还会有一点点小小的问题,我们只希望渲染出来中国区域的label而不是整个数据的label,应该如何做呢?方法有很多,我采取的是简单粗暴的一种:

world.features.map((item, index) => {
    item.properties.name = "";
    return item;
});

添加事件

恭喜你,到现在你已经得到了一个完整的世界地图加中国地图的数据,只差一个事件切换了,放大的时候显示中国地图的数据,缩小的时候隐藏,代码如下:

chart.on("georoam", function(params) {
    var options = chart.getOption(); //获得option对象
    if (!params.originX) return; // 不是缩放就返回
    let zoom = options.geo[0].zoom;
    if (zoom > 4.5) {
        let data = decode(china);
        let worldAndChina = Object.assign({}, world);
        worldAndChina.features = worldAndChina.features.concat(data.features);
        echarts.registerMap("world", worldAndChina);
        options.geo[0].label.show = true;
    } else {
        echarts.registerMap("world", world);
        options.geo[0].label.show = false;
    }
    chart.setOption(options);
});

先揭秘一下解码的方法是如何找到的。

对比两条数据,会发现中国地图中多了一个UTF8Encoding这个属性,之后就全局匹配这个属性,在echarts这个包中找,很快就可以找到相关代码了,复制粘贴就好了。就这么简单。

等你解决完目前的燃眉之急之后,希望你可以回来看看我遇到的哪些坑,好的和不好的,对我们而言,都是宝贵的经验。

方案一:同时控制两个地图

其实刚开始做的方案是想通过两个地图的切换来实现这个效果,放弃这个方案的原因是当我们缩放的时候没有办法控制小地图的外轮廓永远和大地图都是对齐的。 当然,我并没有一步一步的写代码,而是自己拿笔把要做的事情一步步的写了下来,写到缩放的地方走死了,就放弃了。这对于我的思考也是宝贵的经验,以后遇到类似的问题就会直接pass该方案了。

方案二:融合数据

就是你们看到的上面的思路,将两套数据融合了起来,但之前思考的方案是将中国地图的数据塞到中国轮廓的地方,尝试了好多次,数据格式都没有问题,还是不可以。到目前为止,我仍然觉得这种方案理论是可以的,但确实不可以,所以只能采取上面的方式了。

方案三:切换地图->巧妙的绕过需求

刚开始走方案二的时候没有走通,就很沮丧。但需求迫在眉睫,所以想到了一种折中的方案,折中方案可能别的需求会用到,但也有很多漏洞和需要优化的地方,也简单说一下吧。

我们在放大地图查看中国地图的时候,因为屏幕尺寸的原因,我们是看不到中国之外的区域的,所以可以通过监听地图的缩放事件,到达一定比例的时候直接setoption改变地图,将world更改为china,但其中也涉及到了一些问题,比如放大之后按照当前的级别切换到中国地图的级别会是一个很小的级别,需要手动的改一下,不作为该篇文章的主要内容,只贴一下代码:

let prevMap = "";
chart.on("georoam", function(params) {
    var options = chart.getOption(); //获得option对象
    if (!params.originX) return; // 不是缩放就返回
    let zoom = options.geo[0].zoom;
    if (options.geo[0].map == "china" && zoom > 1.3) {
        return;
    }
    let chinaMap = {
        map: "china",
        label: {
            show: false,
            fontSize: 16,
            normal: {
                fontSize: 18,
                show: true,
                color: "#cae3ff"
            },
            emphasis: {
                show: true,
                color: "#cae3ff"
            }
        },
        roam: true,
        itemStyle: {
            normal: {
                areaColor: "#082161",
                borderColor: "#00a8ff",
                borderWidth: 1
            },
            emphasis: {
                areaColor: "#082161"
            }
        },
        zoom: 1.3
    };
    let worldMap = {
        map: "world",
        label: {
            show: false,
            fontSize: 16,
            normal: {
                fontSize: 18,
                show: false,
                color: "#cae3ff"
            },
            emphasis: {
                show: false,
                color: "#cae3ff"
            }
        },
        roam: true,
        itemStyle: {
            normal: {
                areaColor: "#082161",
                borderColor: "#00a8ff",
                borderWidth: 1
            },
            emphasis: {
                areaColor: "#082161"
            }
        }
    };
    if (zoom > 4.5) {
        options.geo = chinaMap;
    } else {
        options.geo = worldMap;
    }
    if (prevMap == "china" && options.geo.map == "world") {
        options.geo.zoom = 4.5;
    }
    prevMap = options.geo.map;
    chart.setOption(options); //设置option
});

如果不是我的领导一直鼓励我,还有别的更好的方案,我可能就止步于方案三了,也不会将此文章呈现在大家面前,也很感激每一个读到此处的你们,阅读者是写作者的动力。

都看到这里了,还不关注一下嘛?以后还有更多的技术分享等着你们喔~

https://zhuanlan.zhihu.com/p/379985536
posted on 2022-10-16 21:59  sunny123456  阅读(2790)  评论(0编辑  收藏  举报