从零开始用ECharts自定义地图绘制完整教程常见坑点如何解决
手把手教你使用echarts自定义地图绘制解决省市边界不准问题
echarts自定义地图绘制实战案例详解如何获取geojson数据并加载显示
echarts自定义地图绘制教程常见报错解决方案及省市区划配置方法
ECharts 自定义地图绘制完全指南:从入门到实战,避开所有坑点
写这篇文章的起因是昨天一个做数据可视化的小李找我,说他折腾了三天,地图边界就是显示不对,最后发现是坐标系对不上。这种问题太常见了,我决定把所有坑点都整理出来,帮你一次性解决。
一、先搞清楚:ECharts 自定义地图到底能做什么
很多人第一次接触 ECharts 地图,都会懵:为什么官方自带的地图不够用?
实际情况是,官方地图确实覆盖了中国大部分省市,但有几个致命问题:
- 行政区划调整后边界对不上(比如撤县设区、行政区划变更)
- 某些特殊区域(比如飞地、争议地区)显示不完整
- 自定义样式需求多,官方地图无法满足
- 需要精确到街道/乡镇级别的微观地图
自定义地图的核心思路就一个:用 GeoJSON 数据替代内置地图,然后告诉 ECharts 怎么渲染。
二、准备工作:你需要什么
2.1 开发环境
基础配置就三样:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>ECharts 自定义地图示例</title>
<!-- 引入 ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
</head>
<body>
<div id="main" style="width: 100%; height: 600px;"></div>
<script src="app.js"></script>
</body>
</html>
2.2 GeoJSON 数据来源
这是最关键的一步。数据来源决定了地图边界准不准。
推荐的数据源:
| 数据源 | 特点 | 适用场景 |
|---|---|---|
| 国家地理信息公共服务平台(天地图) | 官方权威,边界准确 | 国内行政区域地图 |
| 阿里云 DataV GeoAtlas | 按省市下载,方便 | 快速原型开发 |
| 高德地图开放平台 | 包含更多细节 | 城市级精细地图 |
| Natural Earth | 全球数据,精度较低 | 世界地图 |
| 自己用 GIS 软件导出 | 完全自定义 | 特殊需求 |
我一般用的是 DataV GeoAtlas,地址是 http://datav.aliyun.com/portal/school/atlas/area_selector,选好省份直接下载 GeoJSON,开箱即用。
2.3 数据格式要求
GeoJSON 必须包含以下几项:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "北京市",
"adcode": "110000",
"center": [116.407526, 39.90403]
},
"geometry": {
"type": "Polygon",
"coordinates": [[[116.0, 40.0], [116.5, 40.0], ...]]
}
}
]
}
坑点提醒:有些下载下来的 GeoJSON 格式不规范,比如 properties 里缺少 name 或 adcode,这会导致 ECharts 渲染时找不到区域名称。遇到这种情况,手动补一下数据就行。
三、从零实现:第一个自定义地图
3.1 加载 GeoJSON 并注册地图
// app.js
const chart = echarts.init(document.getElementById('main'));
// 异步加载 GeoJSON
fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
.then(response => response.json())
.then(geoJson => {
// 注册地图
echarts.registerMap('china', geoJson);
// 绘制地图
const option = {
series: [{
type: 'map',
map: 'china',
roam: true, // 允许缩放和平移
label: {
show: true,
color: '#333'
},
emphasis: {
label: {
color: '#d35400',
fontSize: 14
},
itemStyle: {
areaColor: '#f39c12'
}
},
select: {
disabled: true
}
}]
};
chart.setOption(option);
})
.catch(error => {
console.error('地图加载失败:', error);
});
运行起来后,你应该能看到一张中国地图,可以缩放、拖拽。
常见报错:如果你看到地图是空的,或者显示”未注册地图”,99% 的原因是 fetch 请求失败或者 GeoJSON 格式不对。先在控制台看 Network 面板,确认数据加载成功。
3.2 添加数据可视化
光有地图没意思,加点数据让它”活”起来。
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}<br/>GDP: {c} 亿元'
},
visualMap: {
min: 0,
max: 100000,
left: 'left',
top: 'bottom',
text: ['高', '低'],
calculable: true,
inRange: {
color: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
}
},
series: [
{
name: 'GDP',
type: 'map',
map: 'china',
roam: true,
label: {
show: true,
fontSize: 10
},
data: [
{ name: '北京市', value: 41610 },
{ name: '上海市', value: 43214 },
{ name: '广东省', value: 110761 },
{ name: '江苏省', value: 116364 },
{ name: '浙江省', value: 73516 },
// ... 其他省份
],
emphasis: {
label: { fontSize: 14 },
itemStyle: { areaColor: '#e74c3c' }
}
}
]
};
坑点提醒:visualMap 的颜色范围要和数据匹配,如果最大数据只有 50000,但你设了 max: 100000,颜色分布会很不均匀。
四、省市区划配置:精确到县的地图
很多人不知道 ECharts 支持到县级,其实只要把 GeoJSON 换成县级数据就行。
4.1 获取县级 GeoJSON
// 下载山东省的 GeoJSON(包含所有县区)
fetch('https://geo.datav.aliyun.com/areas_v3/bound/370000_full.json')
.then(res => res.json())
.then(geoJson => {
echarts.registerMap('shandong', geoJson);
// 绘制县级地图...
});
4.2 县级地图的完整配置
const option = {
title: {
text: '山东省行政区划图',
subtext: '精确到县区',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: function(params) {
return `${params.name}<br/>纬度: ${params.data.lat}<br/>经度: ${params.data.lng}`;
}
},
visualMap: {
min: 0,
max: 200,
left: 'left',
top: 'bottom',
text: ['人口密度高', '人口密度低'],
calculable: true,
inRange: {
color: ['#fff5eb', '#fee6ce', '#fdd49e', '#fdae6b', '#f46d43', '#d73027']
}
},
series: [
{
name: '人口密度',
type: 'map',
map: 'shandong',
roam: true,
zoom: 1.2,
label: {
show: true,
fontSize: 8,
color: '#333'
},
data: [
{ name: '历下区', value: 180, lat: 36.67, lng: 117.02 },
{ name: '市中区', value: 150, lat: 36.65, lng: 116.98 },
{ name: '槐荫区', value: 120, lat: 36.68, lng: 116.92 },
// ... 其他县区
],
emphasis: {
label: { fontSize: 12, color: '#fff' },
itemStyle: { areaColor: '#c0392b', shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.3)' }
},
// 地图样式
itemStyle: {
borderColor: '#ccc',
borderWidth: 1,
areaColor: '#e8e8e8'
}
}
]
};
坑点提醒:县级地图标注名字会重叠,fontSize 要设小一点(8-10px),必要时用 labelLayout: { hideOverlap: true } 隐藏重叠的文字。
五、省市边界不准问题:彻底解决
这是用户问得最多的问题,我也踩过的坑。
5.1 问题表现
- 地图显示的位置和实际不符(比如山东画到了河北的位置)
- 某些区域缺失或边界模糊
- 缩放后出现锯齿或空白
5.2 根本原因
ECharts 地图使用的是 EUC-JP 编码,而很多 GeoJSON 数据是 UTF-8 编码,编码不一致会导致坐标系偏移。
5.3 解决方案
方案一:使用正确的坐标转换
// 转换坐标函数
function convertCoordinates(geoJson) {
return {
type: 'FeatureCollection',
features: geoJson.features.map(feature => {
// 如果是 GCJ-02 坐标,需要转换
const coordinates = feature.geometry.coordinates;
// 这里可以用 proj4 库进行坐标转换
return feature;
})
};
}
方案二:使用高德/百度坐标系转换
// 使用高德坐标转换工具
const coordTransform = {
transformPoint: function(lon, lat) {
// GCJ-02 转 WGS-84
const x = lon - 0.0065, y = lat - 0.006;
const z = Math.sqrt(x * x + y * y) - 0.0002 * Math.sin(y * Math.PI);
const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI);
return [z * Math.cos(theta), z * Math.sin(theta)];
}
};
方案三:直接用已经处理好的 GeoJSON
我推荐直接用 DataV 提供的接口,它们已经处理好了坐标问题:
// 国家级别
https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json
// 省级(以广东为例)
https://geo.datav.aliyun.com/areas_v3/bound/440000_full.json
// 市级(以深圳为例)
https://geo.datav.aliyun.com/areas_v3/bound/440300_full.json
5.4 验证边界是否准确
// 在地图上点击验证
chart.on('click', function(params) {
console.log('点击区域:', params.name);
console.log('坐标:', params.coord);
});
如果点击后显示的名称和位置对不上,检查 GeoJSON 数据的 properties.name 是否和 series.data 中的 name 一致。
六、常见报错及解决方案
6.1 报错:echarts.registerMap is not a function
原因:ECharts 版本问题或 CDN 加载失败。
解决:
<!-- 确保加载完整版 ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<!-- 不要只用 echarts.js,要带 "all" 的版本 -->
6.2 报错:Unknown map series type
原因:series.type 写错了。
解决:确认 type: 'map',不是 'mapChart' 或其他。
6.3 报错:地图显示空白
原因:GeoJSON 数据加载失败或格式错误。
解决:
fetch('your-geojson-url')
.then(res => {
console.log('响应状态:', res.status);
return res.json();
})
.then(data => {
console.log('数据格式:', data.type);
console.log('features数量:', data.features?.length);
echarts.registerMap('myMap', data);
});
6.4 报错:区域名称不显示
原因:series.data 中的 name 和 GeoJSON 中的 properties.name 不一致。
解决:
// 检查 GeoJSON 中的区域名称
geoJson.features.forEach(feature => {
console.log(feature.properties.name);
});
// 确保数据中的名称完全一致
series: [{
data: [
{ name: '北京市', value: 100 }, // 必须是"北京市",不是"北京"
]
}]
6.5 报错:地图不跟随窗口缩放
原因:没有监听窗口大小变化。
解决:
window.addEventListener('resize', () => {
chart.resize();
});
七、实战案例:构建一个完整的疫情地图
// 完整的疫情地图示例
const chart = echarts.init(document.getElementById('main'));
// 加载中国地图
fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
.then(res => res.json())
.then(geoJson => {
echarts.registerMap('china', geoJson);
// 模拟疫情数据
const epidemicData = [
{ name: '湖北省', value: 68128, cases: 48206 },
{ name: '广东省', value: 3023, cases: 1467 },
{ name: '浙江省', value: 1212, cases: 1212 },
{ name: '河南省', value: 1272, cases: 1271 },
{ name: '湖南省', value: 1018, cases: 1018 },
{ name: '安徽省', value: 990, cases: 990 },
{ name: '江苏省', value: 633, cases: 633 },
{ name: '四川省', value: 595, cases: 558 },
{ name: '北京市', value: 489, cases: 434 },
{ name: '上海市', value: 462, cases: 451 },
// ... 其他省份
];
const option = {
title: {
text: '2020年中国新冠疫情地图',
subtext: '数据来源:国家卫健委',
left: 'center',
textStyle: { fontSize: 18 }
},
tooltip: {
trigger: 'item',
formatter: function(params) {
if (params.data) {
return `
<div style="padding: 10px;">
<strong>${params.name}</strong><br/>
确诊病例: ${params.data.cases}<br/>
累计报告: ${params.data.value}
</div>
`;
}
return params.name;
}
},
visualMap: {
min: 0,
max: 70000,
left: 'left',
top: 'bottom',
text: ['高', '低'],
calculable: true,
inRange: {
color: ['#fee5d9', '#fcbba1', '#fc9272', '#fb6a4a', '#ef3b2
');
return Promise.all(geos.map(item =>
fetch(item.url).then(res => res.json()).then(data => ({
level: item.level,
code: item.code,
data: data
}))
));
}
// 渲染所有层级的地图
function renderMap(layers) {
const option = {
visualMap: {
min: 0,
max: 100,
left: 'left',
top: 'bottom',
text: ['高', '低'],
calculable: true
},
series: layers.map(layer => ({
type: 'map',
map: layer.level,
roam: true,
selectedMode: 'single',
label: { show: layer.level === 'world' },
emphasis: { label: { show: true } },
select: { disabled: true },
data: layer.data.features.map(feature => ({
name: feature.properties.name,
value: Math.random() * 100
}))
}))
};
chart.setOption(option, true); // true 表示不合并,完全替换
}
八、性能优化:大数据量地图渲染
当数据量很大时,地图会卡顿。几个优化技巧:
// 1. 使用 dataZoom 限制显示范围
option.series[0].dataZoom = [
{ type: 'inside', start: 0, end: 100 },
{ type: 'slider', start: 0, end: 100 }
];
// 2. 简化 GeoJSON 数据
// 使用 simplifies-js 库简化多边形
const simplifiedGeoJson = simplifyGeoJson(geoJson, 0.1);
// 3. 使用 Web Worker 异步处理
const worker = new Worker('map-processor.js');
worker.postMessage(geoJson);
worker.onmessage = function(e) {
echarts.registerMap('china', e.data);
};
// 4. 延迟加载非活跃区域
const option = {
series: [{
type: 'map',
map: 'china',
progressive: 1000, // 渐进式渲染
progressiveThreshold: 3000,
}]
};
九、总结:避坑清单
最后,把最重要的几点总结一下:
- GeoJSON 数据来源:用 DataV 或天地图,别自己瞎导
- 编码问题:确保 UTF-8,别用 GBK
- 坐标系:确认是 WGS-84,不是 GCJ-02 或 BD-09
- 名称匹配:
series.data[].name必须和 GeoJSON 的properties.name完全一致 - 版本问题:用 ECharts 5.x,别用 4.x(4.x 的地图 API 有差异)
- 异步加载:地图数据一定要异步,别阻塞主线程
- 错误处理:加
try-catch和.catch(),别让用户看到空白
十、下一步
掌握了自定义地图,你还可以:
- 结合 ECharts-GL 做 3D 地图
- 用 Leaflet 或 Mapbox 做更复杂的地图交互
- 结合 D3.js 做自定义投影
有问题欢迎在评论区留言,我会逐个解答。记住,地图绘制最重要的是数据准确,其他都是锦上添花。
这篇文章基于实际项目经验整理,如果你遇到其他问题,可以私信我,我会把常见问题更新到文章里。如果觉得有用,点个赞支持一下!
