ECharts地图开发实战:GeoJSON注册、省市县下钻与visualMap配色全攻略
2026/9/7 8:42:02 网站建设 项目流程

简介:ECharts全国、地市、区县地图js文件是一份完整整理的中国地图几何数据集合,针对官方不再提供地图下载而诞生,主要面向Web前端与数据可视化开发者,帮助其在缺少现成地图数据的情况下快速构建基于地理位置的图表应用。压缩包共343个JavaScript文件,按行政区划代码分类命名,内容覆盖全国所有省份、地级市及主要区县,例如哈尔滨、牡丹江、延边朝鲜族自治州等,整体大小25.79MB,引入后可直接通过ECharts的mapType参数指定对应区域代码进行渲染。目前已有2177人学习使用,下载量验证了它在实际项目中的复用价值。这份资源的核心优势在于省去了手工整合GeoJSON的时间,开发者只需引入对应文件并配置series数据,即可实现着色、提示框、点击联动等交互效果,同时可自由扩展至人口分布、GDP对比、疫情监测等场景,极大地提升了中国地图可视化的开发效率。 做数据可视化项目,尤其是后台管理系统里那些“全国xx分布”“各省xx统计”的大屏看板,十有八九要用到 ECharts 地图。但很多前端同学在本地开发时都会遇到同一个问题:代码照着文档写了,echarts.init也没报错,页面就是一个空荡荡的画布,地图就是不出来。还有个更常见的现象是,网上找到一个老教程,里面引了一个特别的“map/js/china.js”文件就好使,换成 ECharts 5 之后这招又不行了。

这问题说穿了就是一句话:ECharts 本身不负责提供地图边界数据,地图能不能显示,取决于你有没有正确拿到并注册“全国、地市、区县”对应的 GeoJSON 文件。这篇文章我就把这件事完整讲清楚,从数据源怎么选、JS 文件怎么组织,到三级联动的下钻怎么做、visualMap 分段配色怎么调、3D 地图怎么玩,一次性串成一套能直接抄的完整链路。

如果你正在做省级、市级、区县级的地图展示,或者刚被“地图不显示”“下钻回不去”“分了 9 段想改 10 段”这类问题折磨过,这篇文章应该能帮你省下不少排查时间。

1. 先解开最大的误会:地图 JS 文件到底是什么

1.1 你要的不是“魔法 JS”,而是 GeoJSON 数据

很早以前,ECharts 的示例里确实流行直接引入一个china.js,然后echarts.registerMap('china', chinaJson)就能用。那个china.js本质上不是一份“插件”,它里面存的就是一份中国地图的GeoJSON 数据,只不过被包装成了 JS 文件。ECharts 4 时代地图数据还曾内置过一部分,但 ECharts 5 之后官方就把地图数据从主包里拆掉了,明确要求开发者自行准备和注册地图数据。

理解了这一点,你再看网上各种“下载地图 JS 文件”的需求,破解方式就简单了:去找合法的 GeoJSON 数据,然后自己包成 JS 或 JSON,再registerMap注册进去就可以了。不要迷信某个神秘的 JS 文件,一份 GeoJSON 结构无非就是typepropertiesgeometry这些标准字段,性质跟普通业务数据文件没有本质区别。

还有一点容易踩坑:如果你在网上随便下载一份来路不明的全国地图数据,边界可能是不准的、过时的,甚至图中某些区域的归属都是错的。所以无论从合规还是严谨角度,都建议用权威部门公布的标准地图服务,或者主流云服务商提供的合规数据文件,千万不要把个人站点上扒下来的边界数据直接用到正式项目里。

1.2 全国、地市、区县三级数据源去哪拿

我平时用的方案比较固定,首选是阿里云 DataV 的 GeoAtlas 地图选择器。它可以在网页上按省、市、区县点选下载 GeoJSON,同时也提供了比较稳定的接口地址。接口规律很简单:

https://geo.datav.aliyun.com/areas_v3/bound/{adcode}_full.json
  • 100000_full.json:全国地图,里面包含省级行政区边界
  • 340000_full.json:安徽省地图,里面包含安徽各地市边界
  • 340100_full.json:合肥市地图,里面包含合肥各区县边界

这套接口对做三级联动非常友好,因为它的文件命名直接跟行政区划编码(adcode)绑定,你不需要自己去维护一套复杂的“省份-城市编号”字典,只要拿到对应层级的 adcode 就能拼接出数据地址。实际项目里我会把常用的 GeoJSON 下载后放到本地静态目录统一管理,避免线上依赖第三方接口的稳定性。

除了这一种,有些团队也会用高德地图开放平台的行政区划查询 API 来拿 adcode 和边界,这个适合你已经接入高德生态的场景。但注意,高德的接口有配额限制,而且拿回来的数据结构不一定刚好是 ECharts 需要的 GeoJSON 格式,通常还得自己转换一下。天地图也提供标准地图服务,合规性很好,但需要申请 key,而且开发者体验相对重一些,适合对数据权威性要求很高的政务项目。

2. 注册地图与基础渲染:先让第一张图跑起来

2.1 registerMap 注册,以及 geo 和 map 系列的区别

拿到 GeoJSON 之后,第一步是注册。无论是全国、省、市还是区县数据,注册方式完全一样:

import * as echarts from 'echarts'; // 假设 chinaGeoJson 是加载进来的全国地图数据 echarts.registerMap('china', chinaGeoJson); const chart = echarts.init(document.getElementById('mapWrap'));

注册完还只是第一步。接下来要理解 ECharts 里两个容易搞混的入口:geo组件和map系列。简单说,geo是“底图组件”,map是“数据系列”。如果你要做的只是给每个区域填充颜色、展示数值,直接用map系列就够了:

chart.setOption({ tooltip: { trigger: 'item', formatter: (params) => `${params.name}:${params.value ?? '暂无数据'}` }, visualMap: { min: 0, max: 1000, left: 20, bottom: 20, inRange: { color: ['#e8f1ff', '#1890ff', '#09438b'] } }, series: [ { type: 'map', map: 'china', roam: true, label: { show: false }, emphasis: { label: { show: true, color: '#fff' } }, data: [ { name: '北京', value: 328 }, { name: '上海', value: 286 } ] } ] });

这套配置里几个关键点值得展开说。roam: true表示允许用户拖拽和缩放地图,大屏场景下这是刚需。visualMapminmax不要照抄我这里,实际应该根据你业务数据的最大值来调,否则整体颜色会非常平、没有对比度。data数组里的name必须和 GeoJSON 里的区域名称完全一致,比如 GeoJSON 里写的是“北京”,你就不能写“北京市”,否则这个区域不会上色。

geo组件的使用场景不一样。当你想在地图上叠加散点、飞线、气泡或者 3D 图形时,通常是把geo作为坐标系底图,然后让scattereffectScatterlines等系列通过coordinateSystem: 'geo'挂上去。比如要画“某条线路从北京到上海”的飞线效果,地图底子用geo,飞线用lines系列,两者各司其职,代码结构清晰多了。

2.2 一个特别常见的坑:本地直接打开 HTML 会加载失败

很多新手在本地写 demo 时,喜欢直接双击 HTML 文件,用file://协议打开页面,结果发现 GeoJSON 怎么都加载不出来,控制台报跨域错误。这个问题的原因是fetchfile://协议下会被浏览器拦截,不是代码问题,是你本地起服务的方式不对。

解决办法很简单,在项目目录里随便起一个静态服务,比如:

# 用 Python 起一个本地服务 python -m http.server 8080 # 或者用 Node 生态的 npx serve .

然后用http://localhost:8080访问页面,问题立刻消失。这种本地环境问题看着小,但在新手阶段特别消耗排查时间,我印象里第一次遇到这个报错时还以为是 GeoJSON 文件损坏了,折腾了半个多小时才发现是协议问题。

3. 省市县三级下钻:这套交互的核心设计

3.1 整体方案:一级一级按需加载,而不是一次性全量加载

如果你只展示全国一张图,上面那部分配置已经够了。但标题里明确提到“全国、地市、区县”三级,这就意味着要做下钻交互了。我见过的初学者最容易犯的错误,是把全国、各省、各地市、各区县的所有 GeoJSON 一次性全部加载进来,结果页面初始化就卡顿,数据文件体积动辄几十 MB,体验极差。

正确的做法是按需加载。初始只加载全国 GeoJSON,用户点击某个省时,再去加载对应省份的地市 GeoJSON;点击某个市时,再去加载对应城市的区县 GeoJSON。这样每一级的数据都很轻,渲染速度快。这个思路跟后端做懒加载是一样的道理,只拉当前需要的资源。

为了做到按需加载,你需要一张“名称到 adcode”的映射表。这块不用自己手工敲,可以从 GeoJSON 的properties字段里提取。DataV 返回的 GeoJSON 里,每个区域都带adcodename,你第一次拿到全国数据后,循环一遍就能生成provinceAdcodeMap,里面的值就是省级的 adcode,也就是下一级数据 URL 里用的编号。

3.2 点击事件与层级栈管理

下钻的核心逻辑围绕一个点击事件展开。ECharts 的map系列点击事件里,params.name就是当前点击区域的名称。我维护了一个level变量和一个levelStack栈,记录当前是第几级以及每一级的上下文,这样返回的时候才能准确恢复到上一级。

下面这份代码是核心骨架,我加了注释,你可以直接套到项目里:

let level = 0; // 0 全国, 1 省级, 2 地市级 const levelStack = []; // 示例:省份名称到 adcode 的映射,实际可以从全国 GeoJSON 中自动提取 const adcodeMap = { '安徽省': 340000, '浙江省': 330000 // ... }; const MAP_NAME = { 0: 'china', 1: 'province', 2: 'city' }; chart.on('click', (params) => { const name = params.name; if (level === 0) { const adcode = adcodeMap[name]; if (!adcode) return; fetch(`/geo/${adcode}_full.json`) .then((res) => res.json()) .then((geoJson) => { echarts.registerMap(`province_${adcode}`, geoJson); levelStack.push({ level, name, mapName: 'china' }); level = 1; chart.setOption({ series: [ { type: 'map', map: `province_${adcode}`, roam: true, label: { show: false } } ] }, true); }); } else if (level === 1) { const cityAdcode = cityMap[name]; // 从省级 GeoJSON 中提取的市 adcode 映射 if (!cityAdcode) return; fetch(`/geo/${cityAdcode}_full.json`) .then((res) => res.json()) .then((geoJson) => { echarts.registerMap(`city_${cityAdcode}`, geoJson); levelStack.push({ level, name, mapName: `province_${adcode}` }); level = 2; chart.setOption({ series: [ { type: 'map', map: `city_${cityAdcode}`, roam: true, label: { show: false } } ] }, true); }); } });

这里的关键点有两个。一是setOption的第二个参数传true,表示notMerge,也就是让 ECharts 强制用新的配置替换旧配置,而不是合并,否则上一个层级的series配置可能会残留。二是 GeoJSON 的文件名规范,像我这样用adcode作为 map 注册名的一部分,比如province_340000,在多级切换时就不会出现“地图名字冲突”导致渲染错乱的问题。

3.3 返回上一级与交互细节

有下钻就一定要有返回,这个功能在需求文档里很容易被忽略,但实际演示时如果缺了它,业务方第一反应就是“图坏了,回不去了”。我这里用一个面包屑按钮来实现返回,逻辑就是弹栈:

function back() { if (levelStack.length === 0) return; const target = levelStack.pop(); level = target.level; chart.setOption({ series: [ { type: 'map', map: target.mapName, roam: true, label: { show: false } } ] }, true); updateBreadcrumb(); // 更新面包屑文字 }

实际操作时还要注意几个细节。第一,省级下钻到地市后,roam参数要保持开启,因为用户可能想放大查看某个县的边界。第二,每次进入下一级之后,最好把地图的centerzoom重置一下,否则用户上一级把地图拖到了东北角,下钻后地图视野还停留在东北角,看起来就像“白屏”了一样。第三,点击空白区域或者点击海洋区域时,params.name可能是空字符串,要加一层判空逻辑,否则会触发一次无效的下钻请求。

另外,我建议在下钻的同时把标题或面包屑一起更新,比如“首页 / 安徽省 / 合肥市”,这不仅是体验问题,也让调试时能直观地看到当前到底在哪一层,排查地图串层的问题会省力很多。

4. visualMap 分段配色的几个实战细节

4.1 pieces 参数:手控每一段的边界

做地图专题图,最常用的是visualMappiecewise类型,也就是分段图例。它的核心优势是你可以精确控制每一段的取值范围和颜色,而不是像连续型visualMap那样只能看个大概。我实际项目中基本都用分段,因为业务方关心的永远是“哪些省份超过 1000”“哪些在 800 到 1000 之间”,而不是渐变色里某一小段到底对应多少。

一个典型的分段配置长这样:

visualMap: { type: 'piecewise', pieces: [ { min: 1001, label: '1000以上' }, { min: 801, max: 1000, label: '801-1000' }, { min: 601, max: 800, label: '601-800' }, { min: 401, max: 600, label: '401-600' }, { min: 201, max: 400, label: '201-400' }, { min: 0, max: 200, label: '0-200' } ], textStyle: { color: '#333' } }

这里最容易犯的错是分段不连续。比如第一段写了{ min: 0, max: 199 },第二段写{ min: 200, max: 399 },中间本身就连续,但因为某个数据恰好是 199.5,它就会落入“找不到对应分段”的尴尬状态。所以分段边界最好用整数且尽量保证连续性,比如max: 200min: 201。更严谨的做法是先看一遍数据分布,再定每段的边界,不要让某一段里一个数据点都没有。

4.2 “9 段变 10 段”到底改哪里

这个热搜词指向的是一个很实际的改动需求:原来数据分了 9 档,现在业务方说再加一档。原理上很简单,ECharts 的分段数等于pieces数组的长度,你要从 9 段变成 10 段,就往pieces数组里再插入一个区间元素,同时调整原有区间的边界。

但难点往往不在“加一段”这个操作本身,而在于怎么决定新一段的边界。比如原来 0 到 1000 分 9 等份,每段大约 111,现在要分 10 等份,最省事的做法是每段 100。可如果数据本身集中在 0 到 100 区间,你再按 0-100、101-200 这种方式分,会发现 90% 的数据都拍死在第一段,地图上只有一种颜色,这个 10 段图分得毫无意义。

正确做法是先用Math.maxMath.min扫一遍数据,统计出真实分布,再看哪些边界处数据量比较稀疏,把分段点设在那里。比如数据主要集中在 50 以下,那 10 段就可以切成 0-10、11-20、21-30、31-40、41-50、51-80、81-120、121-200、201-500、501 以上这种非均匀区间。分段合理的标准很简单:每一段里都有数据,而且颜色分布能看出明显的地域差异,而不是一大片都是同一个颜色。

4.3 地图上给某些市标记数量的几种做法

这个需求也很常见,就是“全国地图上,不显示所有省份数值,只把某些重点城市标注出来”。实现方式有几种,我按推荐顺序讲。

第一种是直接在map系列的data里把所有城市都列上,不想显示数值的城市valuenull,然后在label.formatter里判断一下,只对value > 0的区域显示数字:

label: { show: true, formatter: (params) => { const value = params.value; return value > 0 ? `${params.name}\n${value}` : ''; } }

第二种是叠加一个scatter散点系列。先用geo组件做底图,然后把目标城市的坐标点作为散点数据画上去,用symbolSizelabel显示数量。这种方式的好处是数量展示更醒目,还能配合气泡大小做视觉增强,适合“重点城市标记”这类强调效果。缺点是需要在数据里准备城市的经纬度坐标,如果你的数据里没有经纬度,得先做一遍坐标匹配,稍微多一步工作。

第三种是直接用series.data里只放需要标记的城市,其余城市不放任何数据。地图上未标记区域会显示默认底色,标记区域则会根据数值上色。这种方式最轻量,缺点是无法对未标记区域做额外的样式控制。

5. 地图长宽比、3D 扩展与高频坑位排查

5.1 地图组件“长方形展示”怎么破

热搜词里有个“echarts 地图组件长方形展示 全球地图”,实际说的是一个问题:地图在画布里显示出来是扁的、被拉伸的,或者只占了一小部分。这个问题的根源在于 ECharts 的地图默认自带一个宽高比(aspectScale),如果你不给它明确的布局空间,它就会按自身比例缩放,可能显得很小或者很奇怪。

常规做法是用geo组件时设置layoutCenterlayoutSize,强行指定地图的中心位置和大小:

geo: { map: 'china', layoutCenter: ['50%', '50%'], layoutSize: '100%', aspectScale: 0.85 }

如果你的地图只显示了一个局部区域,那不是长宽比的问题,而是centerzoom被改过了。比如有人为了定位某个省,把zoom调到 5,结果从全国视角看,整张图就只剩下一个省。这种情况把zoom重置回 1 左右,或者删除center配置就能恢复。

5.2 配合 echarts-gl 做 3D 地图

如果你需要那种带立体感的 3D 地图,目前主流方案是引入echarts-gl。它的设计思路和普通 ECharts 一样,只是多了一些 3D 系列类型,比如geo3Dmap3Dscatter3D

基本的 3D 地图长这样:

import 'echarts-gl'; chart.setOption({ geo3D: { map: 'china', roam: true, itemStyle: { color: '#2a3e52', opacity: 1, borderWidth: 1, borderColor: '#8bb3d9' }, label: { show: true, textStyle: { color: '#fff', fontSize: 12 } }, regionHeight: 4, shading: 'lambert', light: { main: { intensity: 1.2 }, ambient: { intensity: 0.4 } } }, series: [ { type: 'scatter3D', coordinateSystem: 'geo3D', data: cityPointData, symbolSize: (val) => 8 + val / 100, itemStyle: { color: '#f8c445' } } ] });

这里有几个实战心得。一是regionHeight控制的是地图区域拉伸出来的“厚度”,调成 0 就是纯平面,调大了立体感强但视觉上会遮挡周边小城市;二是shading建议用lambert,配合灯光效果能出比较自然的明暗过渡;三是geo3D的性能开销比平面地图高不少,如果你是在低端电脑或移动端上做大屏,不建议满屏用 3D,往往只在首页或者重点省份用一下就好,否则帧率会很难看。

5.3 常见问题速查表

最后把高频问题整理一下,供你按图索骥:

现象大概率原因解决方式
地图空白,什么都不显示没有registerMap,或者系列里map名字和注册名不一致检查注册名是否一致
部分区域不上色数据的name和 GeoJSON 里的name对不上统一用 adcode 关联,或改为全称
本地打开 HTML 加载不出 GeoJSONfile://协议下fetch被浏览器拦截python -m http.server起本地服务
地图显示成了一个局部区域zoom过大或center偏了重置为zoom: 1,删除center
颜色全是一个色,没有分层visualMapmin/max范围不合理根据实际数据 max 调整范围
下钻后地图位置串了没有重置centerzoom每次setOption时重置视角
3D 地图帧率很低geo3D数量太多或硬件性能不足减少使用范围,简化 GeoJSON
图例文字和分段对不上pieces边界不连续或重复检查相邻min/max是否连续且无交集

说到底,ECharts 地图本身并不难,难点全在数据链路和交互设计上。你只要把 GeoJSON 的加载、注册、多级切换这三件事想清楚,做出来的效果已经能超过大部分需求方预期了。

最后再分享一个我自己的习惯:在项目里写一个统一的地图工具模块,把registerMap、按需加载、下钻返回、加载中遮罩全部封装起来,同时给每个层级的数据命名带上 adcode 后缀。这样不管以后项目里是接全国、省、市还是区县,都只需要往映射表里加一条记录,不需要再重写交互逻辑。这个模块写一次,后面能给你省下大量重复劳动。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询