做数据可视化,尤其是地理信息相关的展示,Echarts 一直都是国内的“顶流”选择。但很多初学者或者进阶用户都会卡在一个地方:为什么我引用的地图只有省份,没有城市?为什么我想画某个特定区的边界,结果报错了或者显示一片空白?
别急,今天我们就把这个问题彻底掰开揉碎了讲清楚。我会从一个真正的实战角度,带你理解 Echarts 地图的底层逻辑,然后手把手教你怎么获取 GeoJSON,怎么注册地图,最后怎么画出带区划、带数据、还能交互的精美地图。
为什么默认的地图总是不够用?
首先,你得明白 Echarts 默认提供的地图长什么样。
如果你直接引入 echarts 的 CDN 或者 npm 包,你会发现它内置的地图通常是 省级 或者 市级 的聚合数据。比如你调用 china.json,你只能看到全国34个省级行政区的轮廓。如果你想看“北京市朝阳区”的具体形状,或者“浙江省杭州市”的细分,默认包里没有。
这是因为地理数据的粒度越细,数据体积越大。国家级的 GeoJSON 可能只有几 MB,但如果是全国所有区县级别的详细边界,数据量会轻松超过 50MB,甚至上百 MB。为了性能考虑,官方默认只提供了概览级的地图。
所以,自定义地图的核心,本质上就是替换数据源。我们需要自己去找对应粒度的 GeoJSON 数据,然后用 Echarts 的 registerMap 方法注册进去,替换掉默认的地图。
第一步:去哪里找靠谱的 GeoJSON 数据?
这是最关键的一步,也是踩坑最多的一步。网上有很多 GeoJSON 数据源,但质量参差不齐。
推荐的数据源
阿里云 DataV.GeoAtlas 这是国内做可视化最常用的资源站。它提供了非常清晰的省、市、区县三级边界数据下载,而且坐标体系是标准的 GCJ-02(火星坐标系),这对国内地图显示至关重要。 网址:
http://datav.aliyun.com/portal/school/atlas/area_selector高德地图开放平台 如果你需要实时更新的行政边界,高德的数据源非常权威。虽然它主要提供 API 查询,但也可以配合工具导出 GeoJSON。
Natural Earth 如果你做国际地图,Natural Earth 是国际标准,数据比较简略但准确,适合全球视图。
坐标系的坑:GCJ-02 vs WGS-84
在这里我要特别严肃地提醒你一点:国内地图必须用 GCJ-02 坐标系(火星坐标系)。
Echarts 默认假设数据是 GCJ-02 坐标。如果你从某些国外地图服务(如 Google Maps 原始数据)或者 GIS 软件(如 QGIS 默认输出)导出了 WGS-84 坐标的 GeoJSON,直接放进 Echarts 里,地图会整体偏移,甚至直接偏到大西洋里去。
如何判断和修正?
- 如果你用的是阿里云 DataV 的数据,不用操心,它已经是 GCJ-02 了。
- 如果你从其他来源拿到数据,发现位置不对,可以使用工具(如 coordtransform)进行坐标转换,将 WGS-84 转换为 GCJ-02。
第二步:理解 Echarts 的地图渲染机制
在动手写代码之前,我们需要简单了解 Echarts 是怎么画地图的。
Echarts 的地图模块依赖于一个名为 geoJson 的数据结构。这个 JSON 文件通常包含以下关键部分:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "朝阳区", // 区域名称,用于 tooltip 和查找
"cp": [116.48, 39.92], // 中心点坐标,用于定位
"childNum": 0 // 子区域数量
},
"geometry": {
"type": "Polygon", // 面数据,常见
"coordinates": [[[116.4, 39.9], [116.5, 39.9], ...]] // 边界坐标数组
}
},
{
"type": "Feature",
"properties": { ... },
"geometry": {
"type": "MultiPolygon", // 多面数据,常用于岛屿或飞地
"coordinates": [ ... ]
}
}
],
"name": "北京市" // 整个 GeoJSON 的名称
}
Echarts 在 registerMap 时,会解析这个结构,将 properties.name 作为区域的标识符,将 geometry.coordinates 绘制成路径。
第三步:从零构建一个区县级地图实例
假设我们要做一个 北京市 的地图,展示各区的人口密度数据。
1. 准备数据
我从阿里云 DataV 下载了 100000_full.json(北京市县级边界数据),并重命名为 beijing.json。同时,我准备了一份模拟的人口数据:
// beijing.js - 模拟数据
const beijingData = [
{ name: '东城区', value: 85000 },
{ name: '西城区', value: 78000 },
{ name: '朝阳区', value: 120000 },
{ name: '海淀区', value: 95000 },
{ name: '丰台区', value: 65000 },
{ name: '石景山区', value: 35000 },
{ name: '通州区', value: 55000 },
{ name: '顺义区', value: 45000 },
{ name: '昌平区', value: 60000 },
{ name: '大兴区', value: 58000 },
{ name: '房山区', value: 40000 },
{ name: '门头沟区', value: 25000 },
{ name: '平谷区', value: 22000 },
{ name: '怀柔区', value: 20000 },
{ name: '密云区', value: 15000 },
{ name: '延庆区', value: 12000 }
];
2. 引入 Echarts 和地图数据
在使用 npm 时,你需要确保 echarts 和 echarts-gl(如果做三维)都已安装。这里我们用标准的 Echarts 5.x 版本。
import * as echarts from 'echarts';
import beijingGeoJson from './beijing.json'; // 假设你用了 webpack/vite 等打包工具
// 如果是纯 HTML 引入,则通过 fetch 或 script 标签加载
3. 注册地图
这是最容易被遗漏的一步。你必须在调用 setOption 之前,先注册地图。
// 第一个参数 'beijing' 是地图的唯一标识符,第二个参数是 GeoJSON 数据
echarts.registerMap('beijing', beijingGeoJson);
4. 配置 Echarts 选项
现在我们可以编写核心的配置项了。为了演示完整流程,我会加入地图背景、交互提示、以及地图本身的视觉样式。
const chartDom = document.getElementById('main');
const myChart = echarts.init(chartDom);
const option = {
// 地图主题,可以设置背景色
backgroundColor: '#1a1a2e',
// 提示框组件
tooltip: {
trigger: 'item',
formatter: function(params) {
if (params.data) {
return `<div style="font-weight:bold">${params.name}</div>
<div>人口密度: <span style="color:#ffeb3b">${params.value}</span> 人/km²</div>`;
} else {
return params.name;
}
}
},
// 视觉地图组件,让颜色根据数据值变化
visualMap: {
min: 10000,
max: 150000,
text: ['高', '低'],
realtime: false,
calculable: true,
inRange: {
// 渐变色系:深蓝 -> 青色 -> 黄色 -> 红色
color: ['#0f172a', '#0284c7', '#f59e0b', '#ef4444']
},
textStyle: {
color: '#fff'
},
left: 'left',
bottom: '20'
},
// 地图系列配置
series: [
{
name: '北京市各区人口密度',
type: 'map',
map: 'beijing', // 这里必须和 registerMap 的第一个参数一致
roam: true, // 允许缩放和平移
zoom: 1.2, // 初始缩放比例
// 地图块的样式
itemStyle: {
areaColor: '#334155', // 默认区域颜色
borderColor: '#94a3b8', // 边界线颜色
borderWidth: 1
},
// 鼠标悬停时的样式
emphasis: {
itemStyle: {
areaColor: '#22c55e', // 悬停变绿
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
},
label: {
show: true,
color: '#fff',
fontSize: 12
}
},
// 标签配置,默认显示区域名称
label: {
show: true,
color: '#cbd5e1',
fontSize: 10
},
// 关联数据
data: beijingData
}
]
};
myChart.setOption(option);
5. 遇到“找不到区域”怎么办?
这是新手最常遇到的问题。比如你发现“朝阳区”高亮了,但“密云区”没有显示颜色,或者 tooltip 里显示的是 undefined。
这通常有两个原因:
名称不匹配:GeoJSON 中的
properties.name和你data数组中的name不完全一致。- 比如 GeoJSON 里写的是“密云县”,而你数据里写的是“密云区”。
- 解决方法:打开你的 GeoJSON 文件,用文本编辑器搜索一下所有
name字段,核对一遍名称。Echarts 是严格匹配的。
层级问题:你下载的数据可能只包含市级,或者只包含省级。
- 解决方法:确认你下载的 GeoJSON 文件是否真正包含了区县级数据。有时候文件名写着“北京”,但里面只有 16 个区的聚合,没有细分街道。
第四步:进阶技巧——嵌套地图与交互
有时候,我们不仅需要展示平面的区划图,还需要一种“下钻”体验:点击北京,进入北京内部;点击朝阳,显示朝阳内的街道或重点地标。
虽然 Echarts 原生不支持复杂的嵌套层级跳转(像 Google Maps 那样),但我们可以通过 dispatchAction 和动态更新 series.data 来模拟这种交互。
下面是一个点击区域后,高亮显示该区域并弹出详细信息的例子:
myChart.on('click', function(params) {
// 获取点击的区域名称
const regionName = params.name;
// 你可以在这里发起网络请求,获取该区的详细数据
// 这里为了演示,我们假设有一些预定义的子数据
console.log('你点击了:', regionName);
// 示例:高亮效果可以通过 setOption 更新 emphasis 实现
// 或者简单地刷新地图数据来突出显示
myChart.dispatchAction({
type: 'downplay',
seriesIndex: 0
});
myChart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
name: regionName
});
myChart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
name: regionName
});
});
第五步:性能优化——处理大数据量
如果你要画的是全国所有区县(大约 2800+ 个),GeoJSON 文件可能会达到 10MB 以上。直接在浏览器中加载并渲染,可能会导致页面卡顿甚至崩溃。
这里有几个实用的优化技巧:
1. 数据压缩
GeoJSON 中有很多重复的坐标点。可以使用 d3-geo 或在线工具对 GeoJSON 进行简化(Simplify),减少坐标点数量,从而减小文件体积。对于地图展示来说,肉眼几乎看不出区别,但体积能缩小 50%-70%。
2. 按需加载
不要一开始就加载全国地图。先用 china.json 展示全国,当用户点击某个省份时,再异步 fetch 该省份的区县 GeoJSON,然后调用 echarts.registerMap 注册新地图。
// 伪代码示例
myChart.on('click', function(params) {
if (params.componentType === 'series' && params.seriesType === 'map') {
const provinceCode = getProvinceCodeByName(params.name); // 你的逻辑
fetch(`/api/map/province/${provinceCode}`)
.then(res => res.json())
.then(geoJson => {
echarts.registerMap(provinceCode, geoJson);
// 更新 option 中的 map 属性
myChart.setOption({
series: [{ map: provinceCode, data: getProvinceData(provinceCode) }]
});
});
}
});
3. 使用 Canvas 渲染
在 Echarts 的 init 选项中,指定 renderer: 'canvas'。Canvas 在渲染大量多边形时,性能通常优于 SVG,尤其是在数据量巨大的情况下。
const myChart = echarts.init(chartDom, null, {
renderer: 'canvas'
});
常见问题排查清单
在最后,我整理了一份排查清单,当你遇到地图不显示、位置不对、数据没关联时,按这个顺序检查:
控制台报错了吗?
Map geoJson ... doesn't exist:说明map: 'xxx'里的名字和registerMap第一个参数不一致。Cannot read property 'coordinates' of undefined:说明 GeoJSON 格式有问题,或者数据源损坏。
地图位置偏移了吗?
- 检查坐标系。如果是国内地图,确保 GeoJSON 是 GCJ-02 坐标。尝试将 GeoJSON 中的坐标除以 10 或者加上某个固定偏移量(如果是 WGS-84,不要手动加,要用转换工具)。
数据没对应上吗?
- 打开 GeoJSON 文件,搜索你
data数组里的name,看是否完全一致(包括空格、全角/半角符号)。 - 检查
data数组里的name是否在 GeoJSON 的features中真的存在。
- 打开 GeoJSON 文件,搜索你
地图是空白的吗?
- 检查 DOM 容器
#main是否有宽度高度。Echarts 需要容器有明确的尺寸才能渲染。 - 检查
series配置是否正确嵌套在series数组中。
- 检查 DOM 容器
结语
自定义 Echarts 地图并不是什么黑魔法,它本质上就是数据+配置的工作。只要掌握了 GeoJSON 的来源、坐标系的规范,以及 Echarts 的注册机制,你就能绘制出任何层级的地图——从全国到街道,从平面到三维(配合 Echarts-GL)。
记住,细节决定成败。一个多余的空格、一个错误的坐标系,都可能导致整张地图失效。希望这篇指南能帮你理清思路,下次再遇到地图问题,能够从容应对。
如果你在实际操作中遇到了具体的报错,欢迎把错误信息和你的 GeoJSON 结构发出来,我们可以一起分析。祝你的数据可视化项目顺利!
