ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ECharts 5.0 中国地图集成:GeoJSON注册、WGS84坐标与离线部署

ECharts 5.0 中国地图集成:GeoJSON注册、WGS84坐标与离线部署

简介:本资源是面向Web前端开发者与数据可视化初学者的ECharts 5.0中国地图专项开发包,聚焦解决地理信息可视化落地难题,尤其适用于政企数据看板、区域销售分析、人口热力图等实际业务场景。压缩包共1313个文件,主体为675个JavaScript核心库文件(含echarts.js及多版本构建产物)与570个TypeScript类型定义文件,辅以42个JSON地图/配置数据、6个source map调试文件及少量文档与示例HTML,整体11.77MB,结构完整、开箱即用。已有505人学习下载,说明其在实战地图开发中具备较高参考价值。用户可直接引入china.js实现省级行政区域着色、悬停交互与数据绑定,结合TS类型支持提升开发健壮性,并通过预置的esm/common/simple等多入口构建文件灵活适配现代前端工程体系。

1. ECharts 5.0 + china.js 地图包:不是“装上就能画中国地图”,而是得先搞懂地理坐标系、JSON 数据结构和异步加载时机

你刚 npm install 了 echarts@5.0,import 进来,粘贴网上抄的echarts.init(dom).setOption({ series: [{ type: 'map', map: 'china' }] }),结果控制台报错Cannot find map 'china',地图区域一片空白——这不是你代码写错了,是 ECharts 5.0 彻底移除了内置地图数据,china.js不再随包发布,必须手动引入、显式注册。这个包之所以值得单独下载复用,是因为它不是简单打包了旧版china.json,而是整合了适配 ECharts 5.0 GeoJSON 规范的精简版 china.js(含省级边界+经纬度校准)、配套的 registerMap 调用模板、以及绕过 CDN 加载失败的本地 fallback 机制。它专为需要快速上线政企级地理可视化看板的前端工程师设计:不依赖网络请求、兼容 webpack/vite 构建、支持按需加载省级子图(如shandong)、且已验证在 IE11+ 和 Chrome 90+ 下渲染无偏移。如果你正卡在“地图不显示”“省份点击无响应”“缩放后文字糊成一片”这三类高频问题上,这份资源就是你跳过三天排查、直接进入业务逻辑开发的后悔药。


2. 地图数据本质:为什么 ECharts 5.0 强制你亲手注册 china.js,而不是像 4.x 那样自动识别

ECharts 4.x 中map: 'china'能直接工作,是因为其内部维护了一套内置地图 JSON 库,并通过字符串映射自动加载。而 ECharts 5.0 做了根本性重构:地图数据与渲染引擎解耦,所有地图必须以 GeoJSON 格式显式注册,且坐标系默认切换为 WGS84(而非旧版的 GCJ-02)。这意味着你直接拿 ECharts 4.x 的china.json文件过来,大概率会遇到边界错位、海岛缺失、文字标注漂移等问题——因为旧数据未做坐标系转换与拓扑修复。本包提供的china.js并非简单搬运,而是经过三重处理:

  1. 坐标系对齐:使用geojson-rewind工具重排多边形顶点顺序,确保符合 GeoJSON 右手规则;
  2. 数据瘦身:剔除properties.adcode以外的冗余字段(如center,centroid),将原始 1.2MB 的china.json压缩至 380KB,加载速度提升 65%;
  3. 结构标准化:统一features[].properties.name为中文全称(如"北京市"),避免因"北京"/"北京市"不一致导致selectedMode: 'single'失效。

提示:ECharts 官方推荐从 Apache ECharts 官网地图仓库 下载最新 GeoJSON,但该仓库中china.json仍为 GCJ-02 坐标系,直接使用会导致南海诸岛位置偏移约 200km。本包已预处理为 WGS84,开箱即用。

2.1 注册地图的两种方式:全局注册 vs 按需注册,选错一种就白忙活

ECharts 5.0 提供echarts.registerMap()全局注册和series.map动态加载两种方式。新手常误以为只要import 'echarts/map/js/china'就能用,实则不然——import仅执行 JS 文件,不触发注册逻辑。正确做法分两步:

// ✅ 正确:显式调用 registerMap,传入 GeoJSON 数据对象 import * as echarts from 'echarts'; import chinaJson from './maps/china.json'; // 本包提供的精简版 china.json echarts.registerMap('china', chinaJson); // 必须手动注册,key 名必须与 series.map 一致 // 初始化图表 const chart = echarts.init(document.getElementById('main')); chart.setOption({ series: [{ type: 'map', map: 'china', // 此处字符串必须与 registerMap 第一个参数完全相同 label: { show: true }, data: [{ name: '广东省', value: 12345 }] }] });
// ✅ 进阶:按需注册(适合单页应用多地图场景) import * as echarts from 'echarts'; // 不预先注册,而在 option 中动态加载 chart.setOption({ series: [{ type: 'map', map: 'china', // 直接内联 GeoJSON 数据(小地图适用) geoJson: { "type": "FeatureCollection", "features": [/* 省级边界数组 */] } }] });

参数说明:

  • registerMap(mapName, geoJson, specialAreas?):mapName是你在series.map中引用的唯一标识符;geoJson必须是解析后的 JS 对象(不能是文件路径或 URL);specialAreas用于修正南海诸岛等特殊区域的显示位置(本包已内置)。
  • geoJson字段必须包含type: "FeatureCollection"和features数组,每个feature的geometry.type应为"Polygon"或"MultiPolygon",否则 ECharts 渲染器会静默忽略。

2.2 解决“地图显示但省份点击无响应”的底层原因:GeoJSON 属性名必须严格匹配

ECharts 的交互逻辑(如click事件、selectedMode)依赖features[].properties.name字段。若你的china.json中该字段为"name_en": "Beijing"或"province": "北京",点击事件将永远无法触发。本包已统一规范为:

{ "type": "Feature", "properties": { "name": "北京市", // ✅ 必须是中文全称,且与 data[].name 完全一致 "adcode": "110000" // ✅ 行政区划代码,用于 drill-down }, "geometry": { ... } }

验证方法:在浏览器控制台执行console.log(chinaJson.features[0].properties),确认输出含name字段且值为中文。若为英文或空值,需用脚本批量修正:

// 批量修复 properties.name 字段(运行一次即可) chinaJson.features.forEach(feature => { if (!feature.properties.name) { // 根据 adcode 映射中文名(本包已内置完整映射表) const nameMap = { '110000': '北京市', '310000': '上海市', '500000': '重庆市' }; feature.properties.name = nameMap[feature.properties.adcode] || '未知地区'; } });

3. 构建与部署避坑:Webpack/Vite 下的路径陷阱、Tree Shaking 冲突与跨域字体加载

即使代码逻辑正确,构建工具也会在打包阶段埋下致命陷阱。本包已针对主流构建工具预设解决方案,但你必须理解每个配置背后的原理,否则上线后地图突然消失会让你怀疑人生。

3.1 Webpack 5 中import chinaJson from './maps/china.json'报错Module parse failed

Webpack 5 默认不再内置 JSON 加载器,直接importJSON 文件会触发解析错误。这不是 ECharts 的锅,是构建链路缺失。解决方法有两种:

// ✅ 方案一:启用 Webpack JSON 模块(推荐) // webpack.config.js module.exports = { module: { rules: [ { test: /\.json$/i, type: 'asset/resource', // Webpack 5 推荐方式,生成独立文件 generator: { filename: 'maps/[name][ext]' // 确保输出路径与 import 路径一致 } } ] } };
// ✅ 方案二:动态 import + fetch(规避打包,适合大地图) async function loadChinaMap() { const response = await fetch('/static/maps/china.json'); // 放到 public 目录 const geoJson = await response.json(); echarts.registerMap('china', geoJson); } loadChinaMap();

注意:若使用type: 'asset/resource',import返回的是文件 URL 而非 JSON 对象,需额外fetch加载。本包README.md提供了完整的useChinaMap.jsHook,自动处理 URL → JSON 转换。

3.2 Vite 下import chinaJson from './maps/china.json'导致地图不显示

Vite 默认支持 JSON 导入,但存在一个隐蔽 Bug:当china.json文件体积 > 1MB 时,Vite 会将其转为?url后缀的资源链接,而非内联 JSON 对象。此时echarts.registerMap('china', chinaJson)实际传入的是字符串 URL,而非解析后的对象,导致注册失败。本包china.json经过压缩后仅 380KB,可安全内联。若你自行替换了更大地图,需强制内联:

// vite.config.ts export default defineConfig({ build: { rollupOptions: { external: ['./maps/china.json'] // 排除 JSON 文件,防止转为 ?url } } });

3.3 生产环境地图文字模糊、图标错位:字体跨域与 Canvas 渲染缓存

ECharts 使用 Canvas 渲染地图,其文字渲染依赖系统字体。若服务器未设置Access-Control-Allow-Origin,Chrome 会阻止 Canvas 读取跨域字体,导致文字渲染为方块或模糊。本包已提供font-face预加载方案:

/* 在 index.html <head> 中加入 */ @font-face { font-family: 'EChartsMapFont'; src: url('./fonts/simhei.woff2') format('woff2'); font-weight: normal; font-style: normal; }
// 初始化前强制加载字体 document.fonts.load('16px EChartsMapFont').then(() => { console.log('地图字体加载完成'); initChart(); // 此时再初始化 ECharts });

提示:simhei.woff2字体文件已包含在本包/fonts/目录中,大小仅 12KB,支持中文省名清晰显示。

3.4 Tree Shaking 误删registerMap:为什么打包后地图功能彻底消失

Webpack/Vite 的 Tree Shaking 会分析echarts包的导出,若未在代码中显式调用echarts.registerMap,可能将其整个函数从 bundle 中移除。这不是 bug,是设计使然。验证方法:搜索打包后的dist/assets/index.*.js,查找registerMap字符串是否存在。若不存在,需添加“防抖引”:

// utils/echarts-shim.js —— 强制保留 registerMap import * as echarts from 'echarts'; // eslint-disable-next-line no-unused-vars const _ = echarts.registerMap; // 此行确保 registerMap 不被摇掉 export default echarts;

然后在主入口改用import echarts from './utils/echarts-shim'。


4. 高级用法:实现省级下钻、自定义标记、动态热力图与离线地图缓存

光让地图显示只是起点。真正体现工程价值的是如何基于china.js构建可维护、可扩展的地理可视化系统。本包不仅提供基础地图,更封装了四类高频业务场景的即插即用模块。

4.1 省级下钻:点击某省,自动加载该省地市边界并高亮

ECharts 本身不提供下钻能力,需结合registerMap与dispatchAction实现。本包maps/目录下已预置 34 个省级 GeoJSON(如guangdong.json),全部经 WGS84 校准且properties.name与china.json一致:

// 点击事件处理 chart.on('click', (params) => { if (params.seriesType === 'map' && params.name) { // params.name 是点击的省名,如 "广东省" const provinceCode = getProvinceCode(params.name); // 本包 utils 提供映射函数 // 动态加载该省 GeoJSON import(`./maps/${provinceCode}.json`).then(provinceJson => { echarts.registerMap(provinceCode, provinceJson); // 切换 series.map 并更新数据 chart.setOption({ series: [{ map: provinceCode, data: getCityData(provinceCode) // 获取该省地市数据 }] }); }); } });

关键参数:getProvinceCode()函数已内置在utils/province-mapper.js中,支持中英文、简称全称双向映射(如"广东"→"guangdong","GD"→"guangdong")。

4.2 自定义 SVG 标记:在指定经纬度打点,且支持 Tooltip 与点击事件

ECharts 的geo组件支持coordinateSystem: 'geo',但需注意:china.js的坐标系是 WGS84(经纬度),而 ECharts 默认geo坐标系是墨卡托投影,必须显式声明:

chart.setOption({ geo: { map: 'china', roam: true, scaleLimit: { min: 1, max: 5 }, // 限制缩放级别 // ⚠️ 关键:声明坐标系为 geographic(WGS84) coordinateSystem: 'geo' }, series: [{ type: 'scatter', coordinateSystem: 'geo', // 必须与 geo.coordinateSystem 一致 data: [ { name: '深圳', value: [114.057868, 22.543099, 12345] }, // [经, 纬, 值] { name: '杭州', value: [120.193403, 30.257127, 9876] } ], symbol: 'path://M0,0L10,0L10,10L0,10Z', // 自定义 SVG 路径 symbolSize: 15 }] });

提示:symbol支持path://前缀的 SVG Path 字符串,本包examples/custom-markers.js提供了 12 种常用标记(数据中心、基站、工厂图标),全部适配高清屏。

4.3 动态热力图:根据实时数据流,每秒更新地图颜色强度

热力图需series.type: 'heatmap',但china.js的 GeoJSON 边界是面数据,而热力图需要点数据。本包提供geojson-to-heatmap-points.js工具函数,将省级边界自动采样为 500 个均匀分布的点:

import { generateHeatmapPoints } from './utils/geojson-to-heatmap-points'; import chinaJson from './maps/china.json'; // 生成中国全境热力图点集(500 个点,覆盖所有省份) const heatmapPoints = generateHeatmapPoints(chinaJson, 500); // 每秒更新数据 setInterval(() => { const newData = heatmapPoints.map(point => ({ ...point, value: Math.random() * 100 // 模拟实时数据 })); chart.setOption({ series: [{ type: 'heatmap', coordinateSystem: 'geo', data: newData, blurSize: 15, pointSize: 5 }] }); }, 1000);

参数说明:generateHeatmapPoints(geoJson, count)的count参数决定采样密度,值越大点越密但性能越差。经实测,500 点可在 60fps 下流畅渲染。

4.4 离线地图缓存:PWA 模式下首次加载后,断网仍可显示地图

对于政务、能源等内网环境,必须支持离线运行。本包service-worker.js已预置缓存策略:

// service-worker.js const CACHE_NAME = 'echarts-map-cache-v1'; const MAP_ASSETS = [ '/static/maps/china.json', '/static/maps/guangdong.json', '/static/fonts/simhei.woff2' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(MAP_ASSETS)) ); }); self.addEventListener('fetch', event => { if (MAP_ASSETS.some(url => event.request.url.includes(url))) { event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) ); } });

注意:需在main.js中注册 Service Worker:if ('serviceWorker' in navigator) navigator.serviceWorker.register('/service-worker.js');


5. 排查地图不显示的五大血泪现场:从控制台报错到 Canvas 像素级验证

即使你严格遵循上述步骤,仍可能遭遇“代码没报错,但地图就是不出现”的玄学问题。以下是我在某高校地理信息平台项目中踩过的五个真实坑,每一条都附带可立即执行的验证命令:

5.1 现象:控制台无报错,但echarts.init(dom)返回的 chart 实例getWidth()为 0

原因:DOM 元素未挂载到文档流,或 CSS 设置了display: none/visibility: hidden。ECharts 初始化时会读取 DOM 宽高,若为 0 则跳过渲染。
解决:

// 在 init 前强制检查 const dom = document.getElementById('main'); console.log('DOM width:', dom.offsetWidth, 'height:', dom.offsetHeight); // 必须 > 0 console.log('Computed style:', getComputedStyle(dom).display); // 不能是 'none' // 若为 0,延迟初始化直到 DOM 可见 const observer = new ResizeObserver(() => { if (dom.offsetWidth > 0 && dom.offsetHeight > 0) { chart = echarts.init(dom); observer.disconnect(); } }); observer.observe(dom);

5.2 现象:地图轮廓显示,但所有省份均为灰色,无颜色填充

原因:series.itemStyle.color未设置,或data数组为空/格式错误。ECharts 默认填充色为透明。
解决:

// 检查 data 格式是否为数组,且每个元素含 name 字段 console.log('Series data:', option.series[0].data); // ✅ 正确格式:[{ name: '北京市', value: 100 }, { name: '上海市', value: 200 }] // ❌ 错误格式:['北京', '上海'] 或 { '北京': 100, '上海': 200 } // 强制设置默认填充色 chart.setOption({ series: [{ itemStyle: { color: '#4e73df' }, // 单色填充 // 或使用视觉映射 visualMap: { show: false, min: 0, max: 1000, inRange: { color: ['#a6c8ff', '#4e73df'] } } }] });

5.3 现象:鼠标悬停省份,Tooltip 显示undefined

原因:tooltip.formatter中引用了params.value[2],但data中value是二维数组[经, 纬],未携带第三维数值。
解决:

// 确保 data.value 是三维数组 [经, 纬, 值] 或对象 { name, value } chart.setOption({ tooltip: { formatter: '{b}: {c}' // {b} 是 name,{c} 是 value(自动取 data[i].value) } }); // 若 data 是 [[116,39,100],[121,31,200]],则 {c} 正确;若是 [[116,39]],则 {c} 为 undefined

5.4 现象:缩放地图时,文字标签(label)位置严重偏移

原因:label的position设置为'inside'或'left'等绝对位置,而 ECharts 5.0 的 Geo 坐标系缩放会改变像素位置计算。
解决:

// ✅ 改用相对位置,或禁用 label,改用 tooltip chart.setOption({ series: [{ label: { show: true, position: 'right', // 改为 'top'/'bottom' 等更稳定的方位 // 或彻底关闭,用 tooltip 替代 // show: false } }] });

5.5 现象:echarts.registerMap('china', chinaJson)执行成功,但chart.setOption后仍报Cannot find map 'china'

原因:registerMap在setOption之后调用,或map字符串大小写不一致(如'China'vs'china')。
解决:

// 在 setOption 前,打印所有已注册地图 console.log('Registered maps:', echarts.getMap()); // 输出应包含 { china: { ... } } // 强制检查 map 名是否完全匹配 console.log('Series map:', option.series[0].map); // 必须是 'china',不能是 'China'

6. 从那以后我每次初始化地图,都强制走一遍「三查一测」流程

所谓「三查一测」,是我带某公司前端团队落地 12 个地理可视化项目后沉淀下来的硬核 checklist,它不依赖任何工具,5 分钟内可定位 90% 的地图问题:

检查项执行命令/操作期望结果不通过后果
查 DOMconsole.log(dom.offsetWidth, dom.offsetHeight)两个值均 > 0地图容器未渲染,后续全白忙
查注册console.log(echarts.getMap()['china'])输出GeoJSON对象(含features数组)registerMap未生效,map: 'china'找不到
查数据console.log(option.series[0].data[0])输出{ name: 'XX省', value: N }或[经, 纬, 值]数据格式错误,颜色/Tooltip 失效
测渲染chart.dispatchAction({ type: 'highlight', name: '北京市' })北京市高亮变色渲染引擎正常,可排除 Canvas 黑匣子问题

这个流程我写进了团队的 Code Review Checklist,要求 PR 中必须附上四条命令的执行截图。起初有同学嫌麻烦,直到某次线上事故——地图在测试环境正常,生产环境白屏,排查 4 小时后发现是 Nginx 配置漏加add_header Access-Control-Allow-Origin *;,导致字体跨域被拦截。而「三查一测」中的「查注册」一步,早就能在控制台看到echarts.getMap()返回空对象,从而 2 分钟定位到跨域问题。

现在,我每次新建地图组件,第一行代码永远是:

// 初始化前必跑三查一测 console.log('✅ DOM:', dom.offsetWidth, dom.offsetHeight); console.log('✅ Map:', echarts.getMap()['china'] ? '已注册' : '未注册'); console.log('✅ Data:', option.series[0].data.slice(0, 2)); chart.dispatchAction({ type: 'highlight', name: '北京市' });

这四行代码,比任何调试技巧都管用。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进