echarts-all-map.rar实战指南:ECharts 5地图数据转换、注册与省市下钻 简介ECharts作为百度开源的JavaScript数据可视化库其地图组件是展示地理数据分布的重要工具而官网已不再提供完整的离线地图资源下载。这份echarts-all-map.rar正是面向需要离线使用中国省市地图的Web前端开发者与数据分析师整理的珍贵合集覆盖全国所有省份及地级市。压缩包整体约3.87MB内置README说明文档与map地图数据文件夹其中地图数据多采用JSON等格式存储每个文件对应一个省份或地级市文件名以行政区名称命名便于直接检索与加载。已有151人学习下载适合从事地理数据可视化、需要快速构建地图图表的中高级前端开发者。借助该包开发者可绕开网络依赖在ECharts中通过geo组件加载对应区域的JSON地图数据实现人口分布、经济指标等多维数据的区域映射与交互展示并可按需定制颜色方案、添加标记或组合其他图表组件灵活扩展可视化效果。1. echarts-all-map.rar一份老地图包为什么还能撑起今天的地图可视化如果你最近用 ECharts 做地图可视化大概率撞上过同一个尴尬ECharts 5 之后官方不再内置地图 GeoJSON教程里那种“new 一个 ECharts 实例然后直接显示中国地图”的写法基本凉了。手头这份 echarts-all-map.rar是早期社区流传的“全国省市地图合集”压缩包里面是各省市和全国的 GeoJSON / JS 地图数据。对一个被地图数据来源卡住的前端来说它最大的价值不是“老”而是不用去各个 CDN 碰运气解压后本地注册即可用。这篇文章就按我的落地习惯讲清楚包怎么解、旧格式怎么转、怎么进 Vue/React、哪里最容易踩坑最后给出一份省市下钻的完整方案。适合做 BI 看板、大屏项目或数据可视化的开发想快速把地图能力跑起来的人。2. 解压与选型从 rar 包里挑地图文件再按 ECharts 5 的格式改造2.1 先看包内结构JS 老格式与 JSON 新格式怎么区分先从解压开始。Windows 下双击就能解Linux 和 macOS 上我一般用 7z先装 p7zip。# Ubuntu / Debian sudo apt install p7zip-full # macOS brew install p7zip # 解压到当前目录下的 echarts-all-map 文件夹 7z x echarts-all-map.rar -o./echarts-all-map解压后你会看到一整套地图文件常见命名有china.js、china.json、world.js以及按省级命名的目录里面则是各省的js或json。要判断手头到底是哪一代格式先看文件头。file maps/china.js head -c 200 maps/china.js如果file输出的是 ASCII text / JavaScript source而head看到的内容形如echarts.registerMap(china, {type:FeatureCollection,features:[...]});说明这是 ECharts 3/4 时代的地图 JS 文件它把 GeoJSON 对象直接塞进registerMap调用里依赖页面里有一个全局echarts。另一类json文件则是纯粹的 GeoJSON 数据打开是{type:FeatureCollection, ...}这种在现代工程里直接用import或fetch就能消费。为什么要优先选 JSON原因有三个其一老 JS 文件在模块化工程里常常和按需引入的echarts/core冲突因为它的执行依赖全局对象其二JSON 不包含执行逻辑可以安全地做异步加载和缓存其三后续要替换新版边界数据时JSON 格式直接替换文件即可不需要改代码。我一般会把包内所有 JS 格式的地图都转成 JSON再统一管理。2.2 把老 JS 批量转成 GeoJSON一个 Node 脚本解决网上流传的转换方案多是用正则registerMap\(china, (.*)\)去抓 JSON但地图 GeoJSON 里嵌套大括号和方括号正则最容易被嵌套结构坑。推荐的做法是“括号配对扫描”找到第一个{然后逐字符计数直到括号深度归零取这一整段做JSON.parse。// convert-map-to-json.js // 用法: node convert-map-to-json.js input.js [output.json] const fs require(fs); const path require(path); const input process.argv[2]; if (!input) { console.error(用法: node convert-map-to-json.js input.js [output.json]); process.exit(1); } const code fs.readFileSync(input, utf8); // 老格式地图 JS 基本都有一个大括号开头的 GeoJSON 对象 const start code.indexOf({); if (start -1) { console.error(没有找到 {可能不是地图数据文件); process.exit(1); } let depth 0; let end -1; for (let i start; i code.length; i) { if (code[i] {) depth; else if (code[i] }) { depth--; if (depth 0) { end i 1; break; } } } if (end -1) { console.error(括号没有闭合文件损坏或格式不标准); process.exit(1); } const jsonText code.slice(start, end); try { const geoJson JSON.parse(jsonText); const output process.argv[3] || input.replace(/\.js$/, .json); fs.writeFileSync(output, JSON.stringify(geoJson)); console.log(转换完成:, output); console.log(要素数:, geoJson.features ? geoJson.features.length : N/A); } catch (e) { console.error(JSON 解析失败:, e.message); process.exit(1); }逻辑说明脚本从文件里找第一个{作为 GeoJSON 对象的起点然后做括号配对配对到深度归零的位置就是对象的末尾。这样无论对象前面是registerMap(china,还是var map 都不影响提取结果。相比正则它在处理多层嵌套的coordinates数组时不会提前截断。参数说明input.js是待转换的老地图文件[output.json]是可选的输出路径。不传输出路径时脚本会把输入文件的后缀从.js替换成.json生成在同目录。转换成功后控制台会打印要素数方便你立刻确认文件是否完整。批量转换时直接写个循环for f in maps/*.js; do node convert-map-to-json.js $f done2.3 注册地图与首次渲染最小可用代码转换完成后先跑一个最小例子验证数据没问题不要直接搬进大工程。新建一个 HTML!DOCTYPE html html head meta charsetutf-8 / script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script /head body div idchart stylewidth: 800px; height: 600px/div script // 用 fetch 异步加载本地地图 JSON fetch(./maps/china.json) .then((res) res.json()) .then((geoJson) { echarts.registerMap(china, geoJson); const chart echarts.init(document.getElementById(chart)); chart.setOption({ geo: { map: china, roam: true }, series: [{ type: map, map: china, data: [] }] }); }); /script /body /html这段代码里关键顺序是先fetch拿到 GeoJSON再registerMap最后init并setOption。如果你把init写在registerMap前面ECharts 会在setOption时找不到名为china的地图直接渲染一个空白坐标系。geo.map和series.map的名字必须与registerMap的第一个参数完全一致大小写也要一致。这一步跑通后面接 Vue 和 React 只是换个容器而已。3. 在 Vue 与 React 工程里接入 all-map注册时机、按需加载和更新策略3.1 Vue 3 组件里正确注册地图并渲染框架用法和原生 HTML 唯一的差别在于注册时机与生命周期。Vue 3 组合式 API 里onMounted阶段 DOM 已存在最适合echarts.init而registerMap放在模块顶层或onMounted之前都行但要保证它先于setOption执行。template div refchartRef stylewidth: 100%; height: 560px/div /template script setup import * as echarts from echarts; import { onBeforeUnmount, onMounted, ref } from vue; // 把上一章转换好的 china.json 放进 src/map/china.json import chinaGeoJson from ../map/china.json; const chartRef ref(null); let chart null; onMounted(() { // 注册必须在 init 之前 echarts.registerMap(china, chinaGeoJson); chart echarts.init(chartRef.value); chart.setOption({ tooltip: { trigger: item }, visualMap: { min: 0, max: 1000, left: 20, bottom: 20 }, geo: { map: china, roam: true, label: { show: false }, itemStyle: { areaColor: #f0f2f5 } }, series: [ { type: map, map: china, geoIndex: 0, // 复用 geo 的坐标与样式避免双重渲染 data: [ { name: 广东省, value: 100 }, { name: 江苏省, value: 200 } ] } ] }); }); onBeforeUnmount(() { chart chart.dispose(); }); /script逻辑说明geoIndex: 0表示这个 map 系列直接复用第一个 geo 组件的地图区域避免 geo 和 series 各自渲染一套地图导致样式叠加或事件错乱。如果不想用 geo去掉 geo 配置series 里自己画视觉即可但不能两者同时各写一套相同地图的完整配置。参数说明visualMap负责把数值映射成颜色min/max决定了颜色区间。数据里有超过max的值时建议按业务动态算而不是写死。roam: true开启缩放和平移大屏上一般需要。3.2 React 函数组件里的初始化与销毁React 端我常遇到两个问题useEffect依赖数组里放什么以及组件卸载时没有dispose导致内存泄漏。下面这个写法把初始化放在空依赖的useEffect中数据变化时单独用setOption更新import * as echarts from echarts; import { useEffect, useRef } from react; import chinaGeoJson from ./map/china.json; export default function ChinaMap({ data }) { const chartRef useRef(null); const chartRefInner useRef(null); useEffect(() { // 防止重复注册ECharts 对重复 registerMap 会给出警告 if (!echarts.getMap(china)) { echarts.registerMap(china, chinaGeoJson); } const chart echarts.init(chartRef.current); chartRefInner.current chart; chart.setOption({ geo: { map: china, roam: true }, series: [{ type: map, map: china, geoIndex: 0 }] }); return () { chart.dispose(); chartRefInner.current null; }; }, []); useEffect(() { const chart chartRefInner.current; if (chart data) { chart.setOption({ series: [{ data }] }); } }, [data]); return div ref{chartRef} style{{ width: 100%, height: 560 }} /; }逻辑说明第二个useEffect只负责更新 series 的 data不需要重新 init也不需要重新注册地图。如果依赖数组里写data并且每次都dispose再init组件会先白屏再闪回在大屏上尤其明显。echarts.getMap(china)用来判断地图是否已注册避免重复注册引发的覆盖和警告。3.3 按需加载异步 fetch 地图数据再注册很多团队把地图文件放在静态目录前端用fetch按需拉取。这样首屏不需要加载全国和所有省份的地图数据打开速度能快不少。我自己维护了一个 map-loader// map-loader.js import * as echarts from echarts; const fetchingMap new Map(); export async function loadMap(mapName, url) { // 已注册就直接返回 if (echarts.getMap(mapName)) return; // 防止并发请求同一份地图 if (fetchingMap.has(mapName)) return fetchingMap.get(mapName); const promise fetch(url) .then((res) { if (!res.ok) throw new Error(地图 ${mapName} 加载失败: ${res.status}); return res.json(); }) .then((geoJson) { echarts.registerMap(mapName, geoJson); return geoJson; }) .finally(() { fetchingMap.delete(mapName); }); fetchingMap.set(mapName, promise); return promise; }参数说明mapName是注册名建议和地图数据的行政编码一致。url是地图 JSON 的实际地址比如/map-data/guangdong.json。fetchingMap是请求中的缓存避免用户快速点击两个不同省份时同一份数据被并发加载两次。注册完成后再次调用会走getMap快速返回不再请求网络。需要注意异步注册完成后如果你已经init了图表需要再调用一次setOption让地图切换到新注册的地图名。也就是说loadMap只是保证“地图数据已注册”渲染层的刷新仍由你控制。4. 政区地图的几个坑南海诸岛、地名匹配、坐标偏移4.1 中国地图的南海诸岛显示与替换策略手头 all-map 包里的china.json有两个版本一种在右下角带南海诸岛小图一种不带。不带的那种默认渲染出来右下角是空的观众容易误以为是数据缺失政区展示场景尤其敏感。常见做法是不要自己去拼 GeoJSON而是整体换一份较新的全国边界数据。替换时保持registerMap的 name 不变业务代码一行都不用改。如果你暂时不能换数据也可以用 ECharts 的双 geo 方案主图显示中国大陆南海诸岛单独用小比例尺的geo放在右下角。双 geo 的两个map需要各自注册第二个的center和zoom按南海范围调整。这个方案改动更小但要处理两个 geo 的坐标联动缩放入口多了维护成本。我更推荐前者直接换完整数据。4.2 数据里的地名和地图属性对不上怎么办地图渲染靠的是 GeoJSON 里每个 feature 的properties.name而你的业务数据可能存的是简称、旧称或带后缀的名称。比如数据库里是“广东”地图里是“广东省”数据库里是“内蒙古”地图里是“内蒙古自治区”。最常见的处理有两种。一种是在数据侧对齐写一个字典或直接用adcode关联数据表里存行政区划编码GeoJSON 的properties里也有编码时优先用编码匹配。另一种是前端用nameMap做映射series: [ { type: map, map: china, nameMap: { 广东: 广东省, 广西: 广西壮族自治区, 内蒙古: 内蒙古自治区, 新疆: 新疆维吾尔自治区, 西藏: 西藏自治区, 宁夏: 宁夏回族自治区 }, data: [ { name: 广东, value: 100 }, { name: 内蒙古, value: 200 } ] } ]nameMap会把 series 数据里的name先映射成地图属性里的标准名再去做匹配。注意它只解决改名问题不解决“地图里根本没有这个区域”的问题。如果数据里出现“港澳台”而地图 feature 里分成了“香港”“澳门”“台湾”需要先在数据层拆开不能靠nameMap。4.3 GCJ-02 坐标与底图叠加时的偏移问题ECharts 地图包里的坐标体系大多来自国内的公开地图数据坐标经过偏移加密通常就是 GCJ-02而不是 GPS 原始用的 WGS-84。如果只是用 ECharts 画纯色块地图你看不出任何问题因为渲染的是矢量轮廓本身。但如果你把 ECharts 的地图叠在在线影像底图或和另一个基于 WGS-84 的地图组件并排对齐边界会出现几百米的整体偏移。这不是配置错了是坐标系不一致。我的建议是ECharts 的geo组件只用来做展示和交互不要拿它和 GIS 底图直接叠加。真要和地图引擎叠加让 GIS 侧输出的 GeoJSON 使用同坐标系或者在可视化之前做坐标纠偏。多数做政区统计的前端项目不会走到这一步但一旦涉及多源数据对不齐优先怀疑坐标系而不是代码。5. 避坑指南ECharts 地图不显示的排查清单与修复顺序如果按上面的步骤做地图大概率一次就能出来。但遇到不显示的情况建议按下面的顺序排查每一条都是实际踩过的。5.1 现象地图区域空白只剩坐标轴网格刚接触 ECharts 地图的人常遇到这个。原因有三类占比最高registerMap没有执行setOption里的map字符串和注册名不一致echarts引用对象不是同一个。第三个在模块化工程里最隐蔽——框架里用了按需引入的echarts/core某个组件里又从全局echarts注册两边不是同一个模块实例注册等于白注册。解决先打开控制台执行echarts.getMap(china)返回undefined说明注册没生效返回对象说明注册成功问题在setOption配置。确保全工程统一用import * as echarts from echarts不要混用全局变量和按需引入。5.2 现象控制台提示重复注册切换省份时地图闪白registerMap对同一个名字重复调用ECharts 会直接覆盖并给出警告。如果你在每次点击省份时都无条件注册新地图闪白就是因为新地图注册完成后setOption触发了整图重绘。解决所有注册入口统一走echarts.getMap(mapName)判断已注册就跳过。我之前给的loadMap里已经包含这个逻辑直接复用即可。如果你要在已有图例或 tooltip 上做增量更新用chart.setOption(option, { notMerge: true })前要想清楚哪些配置需要保留不要图省事全量覆盖。5.3 现象JSON.parse 报错提示 Unexpected token地图文件本身是 UTF-8 带 BOM 的情况下JSON.parse会读到开头的\uFEFF而报错。这类文件从 Windows 上下载或从 rar 包里直接拖出来的概率很高。用fetch拿数据时浏览器通常会自动处理 BOM但fs.readFileSync和某些构建工具的 raw loader 会把 BOM 原样带进来。解决在转换脚本里读文件后先做code code.replace(/^\uFEFF/, )或者用JSON.parse(text.replace(/^\uFEFF/, ))。如果你遇到的是“第一行是空行导致的行号偏移”通常也是 BOM 或\r\n换行引起的转 JSON 前统一做一次文本清洗会更省事。5.4 现象同时配置 geo 和 series-map颜色和边界互相覆盖常见写法是把geo和series.map各配一套完整地图结果要么只有 geo 的颜色要么系列数据不显示。这是因为两个组件各自渲染了一份地图图层叠加在一起事件和视觉都乱了。解决只保留一个地图源。推荐保留geo地图底图和 roam 交互然后让 series 通过geoIndex: 0挂到同一个地理坐标系上。反过来的写法也成立保留series.map并去掉geo视觉更简单。二者只能留一个作为主渲染另一个做辅助图层。5.5 现象地图在弹窗或折叠面板里渲染空白容器在init时宽度或高度为 0ECharts 会记录一个无效尺寸之后即使容器展开地图也不会自动恢复。这个坑不只是地图有所有基于 Canvas 的图表都有。解决容器显示后再执行init。如果已经 init 过容器展开后调用chart.resize()。在弹窗场景里我一般用nextTick或者监听弹窗的open事件再初始化避免去猜容器何时可见。这条建议放在排查末尾但它发生的频率一点都不低。6. 进阶基于 all-map 做省市下钻与数据回退6.1 点击下钻与返回上一级的状态管理地图下钻是政区可视化最常用的交互。思路很简单点击省份时用省份名找到对应地图文件loadMap注册后setOption切换地图返回时从栈里弹出上一级重新切换回全国。关键是栈的管理和地图数据的异步加载。// drill-down.js import * as echarts from echarts; import { loadMap } from ./map-loader; // 省份名 - 地图数据文件名 const MAP_KEY_MAP { 广东省: guangdong, 江苏省: jiangsu }; export function bindDrill(chart, onBack) { const stack []; // 记录每一级的点击参数 chart.on(click, async (params) { const name params.name; const mapKey MAP_KEY_MAP[name]; if (!mapKey) return; // 没有对应下钻数据不动作 if (stack.length stack[stack.length - 1].name name) return; // 重复点击同一省份 stack.push(params); try { await loadMap(mapKey, /map-data/${mapKey}.json); chart.setOption({ geo: { map: mapKey }, series: [{ map: mapKey }] }); } catch (e) { stack.pop(); // 加载失败就把栈恢复原状 } }); return function back() { if (!stack.length) return; stack.pop(); const prev stack[stack.length - 1]; const mapName prev ? MAP_KEY_MAP[prev.name] : china; loadMap(mapName, /map-data/${mapName}.json).then(() { chart.setOption({ geo: { map: mapName }, series: [{ map: mapName }] }); // 回到全国时通知外部后续可以刷新其他图表 if (!prev onBack) onBack(); }); }; }逻辑说明stack里存的是点击事件的params所以能拿到地图名称。返回时先弹栈再看栈顶是否存在栈为空说明回到全国。loadMap内部已有去重和缓存重复调用不会重复请求。MAP_KEY_MAP是省名到文件名的映射如果你的 all-map 文件命名和省市标准缩写不一致在这里调整一次即可不用改业务代码。这个方案的边界在于它只解决“地图区域切换”数据联动需要在上层做。比如下钻到广东省后右侧图表要同步展示广东的指标你需要在params.name和实际数据之间建立关联。我的习惯是把当前层级和省份存到一个全局 store由它驱动整个看板更新而不是在下钻函数里塞业务逻辑。6.2 边界数据会过期更新策略与投入判断all-map 包的价值在于“历史完整”但行政边界不是一成不变的。做展示型项目旧边界完全够用做政区统计和涉及实际业务归属的项目边界错误会直接带偏数据聚合结果。我的建议是把地图数据当成业务资源来管理定期用较新的边界数据替换仓库里的 JSON替换时保持registerMap的名字不变功能代码不动。如果团队已经有 GIS 能力也可以直接从内部地图服务导出 GeoJSON走相同流程注册进 ECharts。这个方案值得投入因为地图数据更新一次项目里所有地图组件同时生效。说到底echarts-all-map.rar 是被数据来源卡住时的救急方案而不是终极方案——先把项目跑起来再规划边界数据的迭代路径这是我认为最务实的顺序。上面这套下钻和数据加载方案我在多个大屏项目里复用过最大的教训是地图不显示时先查注册名查容器尺寸再查数据和样式这个排查顺序能少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取