ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ECharts省份地图注册与GeoJSON可视化配置实战指南

2026/9/16 8:10:31 拓冰建站 浏览量
ECharts省份地图注册与GeoJSON可视化配置实战指南 简介面向ECharts地图可视化开发者的实用数据包解决制作中国省份及主要城市地图时缺少标准化地理坐标的难点。资源涵盖中国全境及新疆、四川、黑龙江、广东、云南、西藏、山东等省份的JS格式地图数据每个文件包含对应行政区的边界轮廓与城市经纬度可直接引入项目并通过mapType配置调用。包内共35个JS文件整体压缩包约844KB轻量易用免去了从零爬取GeoJSON或逐一手动维护坐标的麻烦。已有2056人浏览学习适合前端可视化、大数据展示、区域统计分析等场景。使用时可先加载对应区域JS文件再结合ECharts的series-map与visualMap组件即可快速渲染出带省份边界和城市位置的地图支撑热力图、散点图、流向图等多种可视化形态有效提升地图类项目的开发效率。1. 一份各省 JS 地图文件解决 ECharts 地图渲染的最后一公里做数据可视化大屏时最耗时间的往往不是图表配置而是地图数据的获取与处理。很多人在echarts.init之后卡在map: china报错上问题基本都出在地图数据没有正确注册。这套资源是把全国 34 个省级行政区分别打包成单独的 JS 文件每个文件内部是标准 GeoJSON 结构包含省份边界坐标和主要城市经纬度。引入方式很直接script srcshandong.js/script之后echarts.registerMap(shandong, data)就能渲染出山东省轮廓图。适合做省级数据下钻、区域热力分布、城市级散点标注的开发者省去了自己抓取阿里云 DataV GeoJSON 再手动裁剪的流程。2. registerMap 加载机制与 GeoJSON 文件结构2.1 ECharts 地图渲染的基本前提ECharts 本身不内置任何地图边界数据必须通过registerMap将地理数据注册到图表实例中才能使用。这个设计让地图数据和图表逻辑解耦也意味着你需要自行准备 GeoJSON 文件。项目中每个省份的 JS 文件内部结构大致如下// shandong.js 内部结构示意 var shandong { type: FeatureCollection, features: [{ type: Feature, properties: { name: 济南市, center: [117.120098, 36.652069], cp: [117.120098, 36.652069], adcode: 370100 }, geometry: { type: Polygon, coordinates: [ [ [117.118, 36.657], [117.128, 36.659], // ... 顶点坐标序列 ] ] } }] }; // 若通过 script 标签引入还需手动注册 echarts.registerMap(shandong, shandong);这里的关键字段中features数组的每个元素代表一个地级行政区properties.name是区域名称必须与后续series.data中的name一一对应否则数据无法映射到对应区域center是城市中心点坐标常用于标注 label 位置geometry.coordinates存储边界多边形的顶点经纬度序列是绘制轮廓的核心数据。2.2 registerMap 的参数与生命周期registerMap的完整签名是echarts.registerMap(mapName, geoJson, specialAreas)。第三个参数specialAreas用来微调某些区域的显示最常见的是南海诸岛。中国全域地图中若需要精确显示南海区域可以这样处理echarts.registerMap(china, chinaJson, { 南海诸岛: { scale: 1.2, // 放大比例 left: 120, // 相对于正常位置的偏移量 top: 20 } });实际项目中这个参数用的场景不多但做全境地图数据下钻时要留意。South China Sea 区域如果不做位置调整可能会盖住其他省份的交互区域。提示registerMap只需要在图表初始化前调用一次。同一页面多张地图图表可以重复使用同一个mapName不需要重复注册。3. 省份地图的配置项解析与数据映射3.1 series-map 类型的完整配置拿到省份地图数据并注册后图表配置就是关键环节。下面是展示山东省各地级市数据的完整配置var chart echarts.init(document.getElementById(mapContainer)); var option { tooltip: { trigger: item, formatter: function(params) { return params.name br/GDP: params.value 亿元; } }, visualMap: { type: piecewise, // 分段型视觉映射组件 pieces: [ {min: 10000, label: 10000 亿以上, color: #A82822}, {min: 5000, max: 9999, label: 5000 - 9999 亿, color: #D65A4C}, {min: 3000, max: 4999, label: 3000 - 4999 亿, color: #EC9B8D}, {min: 0, max: 2999, label: 3000 亿以下, color: #F6CEC8} ], left: left, bottom: bottom }, series: [{ type: map, map: shandong, roam: true, // 允许鼠标缩放和平移 label: { show: true, fontSize: 10, color: #333 }, emphasis: { label: { show: true, fontSize: 14, fontWeight: bold }, itemStyle: { areaColor: #F5C96B // 鼠标悬停时的区域填充色 } }, itemStyle: { borderColor: #fff, borderWidth: 1 }, data: [ {name: 济南市, value: 11000}, {name: 青岛市, value: 12400}, {name: 烟台市, value: 7800}, // ... 其他地级市 ] }] }; chart.setOption(option);这个配置里每个属性都有明确作用。visualMap.type选择piecewise而非continuous因为省级 GDP 数据往往跨度大分段展示能避免低值区域颜色过于接近。roam: true开启地图缩放与拖拽在省级地图下钻场景中几乎是必须的否则无法查看县区级细节。label.show设为true会展示区域名称通常省级地图直接显示没问题但市级地图如果地名过长建议用formatter做截断处理。3.2 数据映射的两种写法series.data中的 name 用来匹配 GeoJSON 里的properties.name。匹配不上时该区域会被设置为itemStyle的默认灰色。这里有两种数据组织的常见写法。第一种是标准数组格式data: [ {name: 济南市, value: 11000}, {name: 青岛市, value: 12400} ]另一种是分开维护 name 和 value// 便于从接口返回的数组直接生成 var cityList [济南市, 青岛市, 烟台市]; var valueList [11000, 12400, 7800]; data: cityList.map(function(name, index) { return {name: name, value: valueList[index]}; })推荐使用第二种方式因为实际业务中地理名称和数值往往来自两个不同的接口或数据表分开维护再合并成对象数组代码可读性更高。3.3 geo 与 series-map 的配合使用geo组件和series-map类型可以共存用于同时展示散点或航线。典型场景是省份地图上叠加重点城市的业务数据点option { geo: { map: shandong, roam: true, label: { show: false // geo 层不显示城市名避免和标注重叠 }, itemStyle: { areaColor: #DCE5EF, borderColor: #fff }, emphasis: { itemStyle: { areaColor: #B4C5D6 } } }, series: [ { type: map, map: shandong, geoIndex: 1, // 复用 geo 组件的地图绘制不重复渲染 data: [] // 不需要数据 }, { type: effectScatter, // 飞线或涟漪散点 coordinateSystem: geo, data: [ {name: 济南, value: [117.120098, 36.652069, 120]}, {name: 青岛, value: [120.382639, 36.067082, 180]}, {name: 烟台, value: [121.447935, 37.463822, 90]} ], symbolSize: function(val) { return Math.sqrt(val[2]) * 3; // 根据数值映射点大小 }, rippleEffect: { brushType: stroke } } ] };geoIndex: 1的含义是让 map 系列复用第 1 个geo组件的坐标系渲染。此时series-map仍然负责绑定数据但轮廓绘制由geo完成两层的坐标系统一标注的经纬度坐标才能准确落在对应城市位置上。4. 城市经纬度数据在散点图与飞线图中的坐标转换4.1 经纬度坐标与 ECharts 坐标系的对应关系ECharts 地图使用 WGS-84 或 GCJ-02 坐标系取决于 GeoJSON 数据来源。阿里云 DataV GeoJSON 默认采用 GCJ-02火星坐标系标准而 GPS 设备裸数据通常是 WGS-84。直接混用会导致散点偏移在国家级地图上偏移量约 50~100 米在省市级别地图上视觉上还不明显但到了街道级别就会看到标注定位错位。省份包里的城市中心点在properties.center字段中这些坐标和 GeoJSON 边界坐标保持同一坐标系。如果你的业务数据来自移动端 GPS 采集则需要先做坐标转换。常见的转换算法是 WGS-84 转 GCJ-02// WGS-84 转 GCJ-02 function wgs84ToGcj02(lng, lat) { var PI 3.14159265358979324; var a 6378245.0; var ee 0.00669342162296594323; function outOfChina(lng, lat) { return (lng 72.004 || lng 137.8347) || (lat 0.8293 || lat 55.8271); } function transformLat(x, y) { var ret -100.0 2.0 * x 3.0 * y 0.2 * y * y 0.1 * x * y 0.2 * Math.sqrt(Math.abs(x)); ret (20.0 * Math.sin(6.0 * x * PI) 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; ret (20.0 * Math.sin(y * PI) 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0; ret (160.0 * Math.sin(y / 12.0 * PI) 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0; return ret; } function transformLng(x, y) { var ret 300.0 x 2.0 * y 0.1 * x * x 0.1 * x * y 0.1 * Math.sqrt(Math.abs(x)); ret (20.0 * Math.sin(6.0 * x * PI) 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; ret (20.0 * Math.sin(x * PI) 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0; ret (150.0 * Math.sin(x / 12.0 * PI) 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0; return ret; } if (outOfChina(lng, lat)) { return {lng: lng, lat: lat}; } var dLat transformLat(lng - 105.0, lat - 35.0); var dLng transformLng(lng - 105.0, lat - 35.0); var radLat lat / 180.0 * PI; var magic Math.sin(radLat); magic 1 - ee * magic * magic; var sqrtMagic Math.sqrt(magic); dLat (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI); dLng (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * PI); return { lng: lng dLng, lat: lat dLat }; }4.2 散点数据与飞线数据的结构差异在省份地图上叠加城市业务数据散点用effectScatter城市间关联关系用lines。关键点在于coordinateSystem: geo模式下数据中的 value 前两位必须是经纬度顺序是[经度, 纬度]。var linesData [ { fromName: 济南市, toName: 青岛市, coords: [ [117.120098, 36.652069], // 起点坐标 [120.382639, 36.067082] // 终点坐标 ] }, { fromName: 济南市, toName: 烟台市, coords: [ [117.120098, 36.652069], [121.447935, 37.463822] ] } ]; option.series.push({ type: lines, coordinateSystem: geo, zlevel: 2, effect: { show: true, period: 4, // 动画周期单位秒 trailLength: 0.3, // 轨迹拖尾长度 symbol: arrow, symbolSize: 6 }, lineStyle: { color: #E7A05A, width: 1.5, opacity: 0.7, curveness: 0.3 // 弯曲程度0 为直线 }, data: linesData });飞线坐标常见的数据来源是后端接口直接返回城市配对关系前端拿到城市名再查表替换成经纬度。如果省份数据包缺失某个城市中心点可以从高德地图 Web 服务 API 的地理编码接口获取返回结果中的location字段是 GCJ-02 坐标直接可用。4.3 高德与天地图坐标拾取的常见误区很多人在标注城市点位时喜欢用网络上的经纬度工具直接拷贝坐标但要注意坐标精度和坐标系。高德地图的坐标拾取器返回 GCJ-02天地图返回 CGCS2000与 WGS-84 非常接近两者混用在市级地图上偏差可达数百米。建议做法是确认 GeoJSON 边界数据的坐标系如果来自 DataV 则按 GCJ-02 处理后续所有坐标数据统一转换不要出现混合使用。// 使用高德拾取坐标时如果 GeoJSON 来自 DataV直接使用即可 // 如果来自 OSM/GeoJSON 开源社区则需要逆转换 GCJ02 - WGS84提示判断 GeoJSON 坐标系类型最直接的方法是打开文件提取一个边界顶点的经纬度放到高德地图和 OSM 里对比看是否一致。高德一致则为 GCJ-02OSM 一致则偏 WGS-84。5. 省份数据的动态加载与 vue 项目的工程化实践5.1 script 标签加载的局限性与动态注入项目中每个省份是一个独立的 JS 文件用script标签逐个引入在原型阶段没问题但在 Vue 或 React 单页应用里这种全局变量方式会让 ECharts 实例难以管理也容易造成重复注册。更合理的做法是用 fetch 加载 JS 文件内容提取出其中的 GeoJSON 对象再注册。常见的做法是// 动态加载省份地图数据 export function loadMapData(mapName) { return new Promise((resolve, reject) { // 假设地图文件放在 /maps 目录下 fetch(/maps/${mapName}.js) .then(res res.text()) .then(scriptText { // 利用 Function 构造器执行脚本内容获取全局变量 const mapData new Function(scriptText ; return ${mapName};)(); echarts.registerMap(mapName, mapData); resolve(mapName); }) .catch(err reject(err)); }); }这个方法的核心原理是每个省份 JS 文件内部声明了同名变量把文件内容拼接上一条 return 语句再用new Function执行就能拿到文件里的原始对象。省去了改用 JSON 文件的迁移成本原始资源可以直接复用。在 Vue 组件中使用时需要注意查看页面加载时序export default { data() { return { chart: null }; }, async mounted() { this.chart echarts.init(this.$refs.mapEl); await loadMapData(shandong); this.renderMap(); }, beforeDestroy() { if (this.chart) { this.chart.dispose(); this.chart null; } }, methods: { renderMap() { this.chart.setOption({ /* 省份地图配置 */ }); } } };beforeDestroy中dispose()释放图表实例是经常被忽略的步骤多页面切换时容易造成内存泄漏。5.2 mapType 与区域下钻的数据流设计省级地图做下钻交互时点击某个城市跳转到市级地图需要维护一张层级关系表。通常按省份行政编码来匹配但需要注意 GeoJSON 中adcode字段的粒度层级数据来源示例省级本资源中的省份文件shandong.js地市级DataV 市级 GeoJSON3701.json区县级在线服务或第三方包370102.json点击省份内的城市时通过params.name拿到城市名再去加载对应城市的 GeoJSON 数据。示意图this.chart.on(click, function(params) { // params.name 为 济南市 const adcodeMap { 济南市: 370100, 青岛市: 370200 }; const adcode adcodeMap[params.name]; if (adcode) { loadMapData(city/${adcode}).then(() { // 重新设置 series 的 map 字段 option.series[0].map adcode; option.series[0].data cityData[adcode]; chart.setOption(option, true); // true 表示完全覆盖旧配置 }); } });setOption的第二个参数为true时会清除并重建整个图表配置避免了旋转、缩放状态被保留后产生错乱但也会丢失用户当前的缩放位置需要在下钻前记录chart.getOption().geo[0].center和zoom并手动恢复。5.3 3D 地图扩展与 echarts-gl 的约束条件echarts-gl 提供map3D系列可以渲染立体的省份地图。但需要明确一点map3D对 GeoJSON 数据的依赖性远高于 2D 地图它要求每个区域的边界必须是合法的 Polygon 或 MultiPolygon不能存在自相交或重复顶点。省份文件里如果包含大量岛屿的 MultiPolygon在 3D 渲染时可能出现破面和高度异常。这时候建议先对 GeoJSON 数据做简化// 使用 simplify-js 简化边界顶点 const simplified simplify(geoJson.features, 0.01, true); echarts.registerMap(mapName, { type: FeatureCollection, features: simplified });简化容差 0.01 表示删除距离小于 0.01 度的顶点。这个操作会减小文件体积但也会让细碎的海岸线变得平滑视觉上通常无感知适合用于 3D 地图场景。实操中常见错误是直接在 2D 地图配置里加map3D要记住map3D不能和series中已有的map系列同时绘制需要把之前的 2D 系列移除。参考配置var option3D { grid3D: {}, xAxis3D: { type: category, data: provinceData.map(item item.name) }, yAxis3D: { type: category, data: [区域] }, zAxis3D: {}, visualMap: { max: 200, inRange: { color: [#B4D5FE, #2A6EB8] } }, series: [{ type: bar3D, coordinateSystem: cartesian3D, data: provinceData.map(function(item) { return [item.name, 区域, item.value]; }) }] };这个配置本质是三维柱状图而非真正的地图立体效果适合展示省份间数据对比。若要做城市地形起伏层面的 3D 可视化需要高程数据与当前资源不匹配不建议在这个资源内扩展。本文还有配套的精品资源点击获取