简介:这份资源是面向 ECharts 地图可视化开发者的湖南省行政区划 JSON 数据包,覆盖全省各市及下辖区县,适合需要在项目中快速接入湖南地图的前端与数据可视化人员使用。压缩包为 rar 格式,体积约 631KB,文件以 JSON 地图数据为主,按市、县层级组织,解压后可直接放入项目供 ECharts 调用,无需自行绘制或转换边界数据。使用时配合 ECharts 官方地图示例,即可实现省、市、区县多级地图的可视化展示,例如需要长沙市区县地图时,单独取出对应 JSON 引入即可。资源已有 2163 人学习下载,说明其在同类地图数据中具备一定实用性与参考价值,能帮助开发者省去地图数据搜集与格式整理环节,把精力集中在图表配置与业务呈现上。
1. 从一张“点不亮”的湖南地图说起
前阵子帮一个做区域业务看板的朋友排查问题,页面里用 echarts 画湖南省地图,结果地图区域一片空白,控制台只丢一句Map hunan not exists。他以为是 echarts 版本问题,来回升降级折腾了一下午,最后发现根因特别朴素:项目里只引了echarts.min.js,却没注册任何 GeoJSON 地图数据。echarts 从 5.x 起把内置地图全部剥离,任何省份、城市、区县地图都得自己准备 JSON 并registerMap。这份「echarts 湖南省 json 地图(包含各市、县地图)」资源,解决的正是这个高频卡点:它把湖南省整体轮廓、下辖各地级市、以及市下面的区县边界数据打包成可直接喂给 echarts 的 JSON,省掉你自己去公开地理数据源抓取、裁剪、坐标纠偏的环节。适合做区域数据大屏、业务分布热力图、下钻联动看板的前端和数据同学,也适合只想快速验证一个地图 demo 的开发者。下面按“数据怎么组织、怎么注册、怎么下钻、坑在哪”一路拆。
2. 地图 JSON 的结构与注册机制:先搞懂 echarts 认什么
2.1 GeoJSON 与 echarts 的 registerMap 契约
echarts 不直接吃任意 JSON,它认的是符合 GeoJSON 规范的地理数据,核心是type: "FeatureCollection"加一组features,每个 feature 带properties.name和geometry。registerMap('hunan', geoJson)做的事,就是把这个名字和这份几何数据绑定到全局地图注册表里,之后series.map: 'hunan'才能找到它。所以拿到资源包后第一件事不是急着写 option,而是打开一个 JSON 看三样东西:顶层是不是 FeatureCollection、features 里每个properties.name是不是中文地名、geometry 的坐标是不是经纬度(一般是[lng, lat]顺序)。这三样任何一样不对,地图要么不显示,要么显示成一团乱线。
常见做法是先用 Node 快速体检一遍,避免把问题带进浏览器:
// check-geo.js Node 环境快速校验地图 JSON 结构 const fs = require('fs'); const path = './hunan.json'; const geo = JSON.parse(fs.readFileSync(path, 'utf8')); console.log('顶层类型:', geo.type); // 期望 FeatureCollection console.log('要素数量:', geo.features.length); // 湖南一般 14 个市州 const names = geo.features.map(f => f.properties && f.properties.name); console.log('地名样例:', names.slice(0, 5)); // 抽查第一个要素的坐标维度,判断是经纬度还是投影坐标 const firstCoord = geo.features[0].geometry.coordinates[0][0][0]; console.log('坐标样例:', firstCoord); // 期望 [110~114, 24~30] 量级这段脚本的逻辑很直白:type不对说明不是标准 GeoJSON,需要转换;features.length能反推这份文件是省级还是市级(市级文件通常只有几个到十几个要素);坐标样例如果出现几十万、几百万这种量级,说明是投影坐标(如 Web 墨卡托),直接塞给 echarts 会跑到画布外,得先做经纬度转换。参数上唯一要改的是path,指向你实际解压出来的文件。我一般会把省、市、县三级文件都跑一遍,确认命名一致——比如省级叫hunan.json,市级可能叫changsha.json、zhuzhou.json,县级再细一层,命名不统一是后面下钻联动翻车的头号原因。
2.2 省、市、县三级文件的组织方式
这类资源包通常按行政层级分目录,省级一个文件,市级按市名各一个文件,县级再按区县名拆分。理解这个层级关系,决定了你后面下钻逻辑怎么写。省级地图用于首屏总览,点击某个市后加载对应市级 JSON 重新registerMap并setOption,再点区县继续下钻。关键点在于:每次下钻都要用不同的 map 名字注册,不能复用同一个名字反复覆盖,否则 echarts 内部缓存会导致图形错乱。我一般用hunan、hunan-changsha、hunan-changsha-yuelu这种带层级前缀的命名,既避免冲突,也方便在代码里根据当前层级反推该加载哪个文件。
// 下钻时按层级拼接 map 名,避免同名覆盖 function mapNameOf(level, code) { // level: 'province' | 'city' | 'county' return level === 'province' ? 'hunan' : `hunan-${code}`; } // 注册前先判断是否已注册,已注册则跳过,减少重复解析开销 const registered = new Set(); async function ensureMap(name, url) { if (registered.has(name)) return name; const geo = await fetch(url).then(r => r.json()); echarts.registerMap(name, geo); registered.add(name); return name; }mapNameOf用层级加编码拼名字,ensureMap用Set做幂等注册——这是血泪经验:下钻再返回上级时,如果不做幂等判断,每次返回都重新 fetch 并 register,数据量大时会有明显卡顿。url参数指向资源包里对应层级的 JSON 路径,name就是拼出来的 map 名。注意fetch在本地file://协议下会被浏览器拦截,调试时务必起一个静态服务,这是新手最常踩的第一个坑,后面避坑章节还会展开。
3. 从零跑通一张湖南地图:option 配置与视觉映射
3.1 最小可运行示例与关键参数
拿到 JSON 并确认结构无误后,先别急着做下钻,用最小配置把省级地图点亮,确认数据链路通了再往上叠功能。下面这段是能直接抄的骨架,重点看geo和series两块的配合:
// 最小可运行:注册湖南地图并渲染 import * as echarts from 'echarts'; import hunanGeo from './geo/hunan.json'; echarts.registerMap('hunan', hunanGeo); const chart = echarts.init(document.getElementById('map')); chart.setOption({ tooltip: { trigger: 'item', formatter: '{b}' }, // {b} 取地名 geo: { map: 'hunan', roam: true, // 允许缩放拖拽 zoom: 1.1, // 初始缩放,避免贴边 label: { show: true, fontSize: 10 }, itemStyle: { areaColor: '#e8f0fe', borderColor: '#8ab4f8', borderWidth: 1 }, emphasis: { itemStyle: { areaColor: '#fdd663' } } }, series: [{ type: 'map', map: 'hunan', geoIndex: 0, // 与上面的 geo 共用坐标系 data: [ { name: '长沙市', value: 120 }, { name: '株洲市', value: 80 } ] }] });逻辑说明:geo负责底图渲染和交互(缩放、标签、描边),series负责数据着色。geoIndex: 0让 series 复用 geo 的坐标系,这样数据热力才能准确贴到对应行政区上。参数上,roam: true打开缩放拖拽,大屏场景常关掉防止误操作;zoom初始值建议 1.0~1.2,太小地图缩在角落,太大边缘被裁;label.show在省级层面建议开,到县级层面要素太密要关掉,否则文字糊成一片。data里的name必须和 JSON 里properties.name完全一致,差一个字(比如“长沙市”写成“长沙”)这块区域就不会着色,这是最高频的“数据不生效”原因。
3.2 visualMap 分段着色与数据对齐
真实业务里数据值跨度大,直接线性映射会让大部分区域颜色趋同,看不出差异。用visualMap做分段(piecewise)比连续(continuous)更直观,尤其适合业务分级场景,比如按订单量分“高/中/低”三档。
// 分段着色:按数值区间给不同颜色 visualMap: { type: 'piecewise', min: 0, max: 200, splitNumber: 4, orient: 'horizontal', left: 'center', bottom: 10, inRange: { color: ['#d4e8ff', '#8ab4f8', '#3b6fd4', '#1a3d8f'] }, text: ['高', '低'], calculable: true }splitNumber控制分几档,inRange.color是从低到高的色带,orient和left/bottom决定图例位置。这里有个容易忽略的点:visualMap的min/max要覆盖你data里的实际值域,否则超出范围的值会被截断成端点色,看起来像“数据错了”。我一般会先算一遍数据的 min/max 再填,或者干脆不写 min/max 让 echarts 自适应,但自适应在数据全为 0 时会出现色带异常,稳妥做法还是显式给范围。数据对齐方面,建议在渲染前做一次名称校验,把data里的 name 和 JSON 的 name 集合做交集,缺的补 0、多的剔除,避免“有数据但地图不亮”的玄学问题。
4. 市级、县级下钻联动:事件、异步加载与返回栈
4.1 点击事件驱动的下钻流程
下钻的本质是“点击某区域 → 拿到该区域编码或名称 → 加载对应 JSON → 重新注册 → 更新 option”。echarts 的chart.on('click', ...)能拿到点击项的名称,但名称到文件名的映射需要你自己维护一张表,因为中文名和拼音文件名不一定对得上。
// 维护名称到文件编码的映射,避免中文名直接拼路径 const cityCodeMap = { '长沙市': 'changsha', '株洲市': 'zhuzhou', '湘潭市': 'xiangtan' // ... 其余市州 }; chart.on('click', async (params) => { const cityName = params.name; const code = cityCodeMap[cityName]; if (!code) return; // 点到非市级区域直接忽略 const mapName = `hunan-${code}`; await ensureMap(mapName, `./geo/city/${code}.json`); chart.setOption({ geo: { map: mapName }, series: [{ map: mapName, data: [] }] // 清空旧数据,等待新数据填充 }); });cityCodeMap是下钻的“路由表”,没有它你就得靠中文名猜文件名,一旦资源包命名规则变了就全崩。ensureMap复用上一节的幂等注册。setOption里同时更新geo.map和series.map,并清空data,否则上一级的数据会残留到下一级,出现“长沙的数据画在株洲地图上”的诡异现象。注意setOption默认是合并模式,切换地图时建议加{ notMerge: true }彻底替换,避免旧配置残留。
4.2 返回上级与面包屑状态管理
下钻容易,返回难。用户点了几层之后要能退回去,就得维护一个层级栈。我一般用一个数组记录访问路径,每次下钻 push,返回时 pop 并重新加载上一级。
// 层级栈:记录 [ {name, mapName, url} ] const stack = [{ name: '湖南省', mapName: 'hunan', url: './geo/hunan.json' }]; async function drillDown(cityName, code) { const mapName = `hunan-${code}`; await ensureMap(mapName, `./geo/city/${code}.json`); stack.push({ name: cityName, mapName, url: `./geo/city/${code}.json` }); renderMap(mapName); } async function goBack() { if (stack.length <= 1) return; // 已在省级,不再返回 stack.pop(); const prev = stack[stack.length - 1]; await ensureMap(prev.mapName, prev.url); renderMap(prev.mapName); }stack的每一项存了名称、map 名和 URL,返回时直接取上一项重新注册渲染,不用重新推导。renderMap是你封装的统一渲染函数,负责setOption和更新面包屑。这里的关键是返回时也要走ensureMap,因为用户可能从别的入口清过注册表,幂等判断能兜住。面包屑 UI 直接读stack渲染即可,点击某一级就连续pop到那一层。县级下钻同理,只是 URL 换成./geo/county/xxx.json,编码表再细一层。层级深了之后要注意内存,注册过的地图不会自动释放,页面停留很久的看板建议限制下钻深度或做手动清理。
5. 避坑与排查:地图不显示、错位、卡顿的常见根因
5.1 地图一片空白,控制台报 Map xxx not exists
现象:setOption后地图区域空白,控制台明确提示地图未注册。原因通常有三:一是只引了 echarts 没引 JSON;二是registerMap的名字和series.map不一致(大小写、连字符);三是 JSON 加载失败但被try/catch吞掉了。解决:先在registerMap后打印echarts.getMap('hunan')确认注册成功,再核对 option 里的 map 名逐字符一致。异步加载场景要确保registerMap在setOption之前完成,用await或回调串起来,别让两个异步操作赛跑。
5.2 地图显示但形状扭曲、跑到画布外
现象:地图能出来,但被拉长、压扁或只露出一角。原因基本是坐标系不匹配——JSON 里是投影坐标(数值几十万级),echarts 按经纬度解析,自然错位。解决:用第 2 章的体检脚本看坐标量级,若是投影坐标,需要先转成 WGS84 经纬度再注册。另一种情况是geo.boundingCoords或center/zoom设错,手动指定了不合适的中心点。稳妥做法是不写center,让 echarts 自动适配,只调zoom。
5.3 数据着色不生效,部分区域始终是默认色
现象:data里明明有某个市的数据,地图上那块却不着色。原因九成是名称不匹配:JSON 里是“长沙市”,data 里写“长沙”;或者 JSON 用了“湘西土家族苗族自治州”这种长名,data 里简写了。解决:渲染前打印 JSON 的 name 集合和 data 的 name 集合做 diff,缺的补、多的删。这个校验我建议固化成工具函数,每次数据更新都跑一遍,比肉眼核对靠谱。
5.4 下钻后返回,地图错乱或数据串层
现象:下钻再返回,地图显示成上一级的形状但数据是下一级的,或者图形重叠。原因是没有用notMerge: true,setOption合并模式把新旧配置混在一起;或者 map 名复用了同一个导致缓存冲突。解决:切换层级时setOption(option, { notMerge: true }),map 名按层级加前缀保证唯一,返回时重新注册上一级地图。
5.5 本地打开 HTML 地图不加载
现象:直接双击 HTML 文件,地图空白,控制台报跨域或 fetch 失败。原因:file://协议下浏览器禁止fetch本地 JSON。解决:起一个静态服务,npx serve或python -m http.server都行,通过http://localhost访问。这个坑几乎每个新手都会踩一次,记住“地图 JSON 必须走 HTTP”就行。
6. 进阶技巧:用 geoJSON 裁剪与按需加载压住首屏体积
资源包把省、市、县全量打包,文件数量不少,如果首屏就把所有 JSON 都请求一遍,弱网环境下白屏时间会很难看。我的习惯是首屏只加载省级文件,市级和县级按需懒加载,配合ensureMap的幂等缓存,用户不下钻就不请求。具体做法是把ensureMap的fetch包一层 loading 状态,下钻时先显示骨架再渲染,避免点击后长时间无反馈。
另一个实用技巧是坐标精度裁剪。公开地理数据往往保留 6 位以上小数,对屏幕渲染来说 4 位足够,多出来的位数纯属浪费带宽。可以用脚本对 JSON 做一次精度压缩:
// 递归压缩坐标精度到 4 位小数,减小文件体积 function roundCoords(node, digits = 4) { if (Array.isArray(node)) { // 判断是否为坐标对 [lng, lat] if (typeof node[0] === 'number') { return node.map(n => +n.toFixed(digits)); } return node.map(n => roundCoords(n, digits)); } return node; } const compressed = { ...geo, features: geo.features.map(f => ({ ...f, geometry: { ...f.geometry, coordinates: roundCoords(f.geometry.coordinates) } })) };roundCoords递归遍历坐标数组,遇到数字对就toFixed(4)再转回数字,digits可按需调到 3 或 5。实测这类压缩能把文件体积降一截,视觉上几乎无差别,代价是极少数超小行政区边界可能略微简化,做精细边界展示时要谨慎。压缩后的文件建议另存一份,别覆盖原始数据,方便出问题时回退。
验证方面,我一般会做两件事:一是用echarts.getMap(name)确认注册表里有数据且 features 数量对得上;二是在浏览器里逐个点击下钻再返回,确认层级栈和地图切换都正常。这套流程走下来,基本能覆盖 90% 的线上问题。从那以后我每次接地图需求,都强制先跑一遍结构体检脚本再写 option,省下的排查时间远比脚本本身多。希望帮到你。
本文还有配套的精品资源,点击获取