嘿,朋友!是不是经常在报表系统里看到那种花哨的地图,心里琢磨:“我也想要一个这样的!” 结果一试,要么是数据死活显示不出来,要么是点的位置歪到马里亚纳海沟去了,或者是干脆报错说“找不到这个省份”。
别慌,今天咱们就坐下来,泡杯茶,我把这层窗户纸给你捅破。咱们不整那些虚头巴脑的理论,直接上手,从注册地图、处理坐标,到解决最常见的投影坑,一步步带你做一个能用的、好看的、企业级的全国网点分布图。
第一步:你得先有张“地图”
Echarts 本身是个强大的绘图引擎,但它默认只带了几种简单的中国地图。如果你要做企业网点分布,通常有两种情况:要么是你需要更详细的省市边界(比如精确到区县),要么是你需要特定的行业地图。
这里有个关键误区:很多人以为 Echarts 的地图是现成的图片,其实它是 GeoJSON 数据。
想象一下,你要在墙上挂一幅画,首先你得有画布和画框。在 Echarts 里,这个“画布”就是注册的地图数据。你可以通过几个渠道获取:
- 阿里云 DataV.GeoAtlas:这是最常用的免费资源,可以直接下载省、市、区县的 GeoJSON。
- 官方示例:Echarts 官方仓库里有一些示例数据。
- 自己转换:如果你有 shapefile (.shp) 文件,可以用 QGIS 转换成 GeoJSON。
假设你要做一个全国省级网点分布,下载好 china.json 后,第一步代码长这样:
// 引入 Echarts
import * as echarts from 'echarts';
// 假设你已经下载了 china.json 并放在了 src/assets/map/china.json
// 这里模拟 fetch 请求获取数据
fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
.then(response => response.json())
.then(chinaJson => {
// 关键一步:注册地图!不注册,Echarts 就不知道你是谁
echarts.registerMap('china', chinaJson);
initChart(chinaJson);
});
注意看这个 echarts.registerMap('china', chinaJson)。这里的 'china' 是你自定义的名字,后面你在配置项里用的 map: 'china' 必须和这里一致。很多初学者报错“地图数据为空”,90% 都是因为这里没注册,或者名字写错了。
第二步:为什么你的点“跑偏”了?—— 投影转换的真相
这是最常见的坑!你拿着一个城市的经纬度(比如北京:116.407526, 39.904030),直接扔进 geoCoord 里,结果发现点不在北京,而在海上,或者根本不在中国境内。
原因很简单: Echarts 的默认中国地图投影方式是 Equirectangular(等距圆柱投影),而且它的坐标系原点可能在 (0,0) 附近,并不是标准的 WGS84 经纬度坐标系直接映射。
更坑爹的是,不同来源的 GeoJSON 数据,其内部的坐标系统可能都不一样。有的用的是 GCJ-02(火星坐标),有的用的是 WGS84。如果你下载的地图数据和你的网点坐标数据来源不一致,那肯定对不上。
解决方案有三种,我推荐第一种,最稳:
方案一:使用 Echarts 自带的 China 地图 + 坐标系转换
如果你不想折腾复杂的投影,最简单的办法是使用 Echarts 官方示例中经过处理的、坐标体系相对统一的 china.json。对于你的网点坐标,你需要确保它们也是经过相同投影处理的。
但是,大多数时候我们用的是真实的经纬度。这时候,我们需要一个“翻译官”——坐标转换工具。
// 这是一个简化的坐标转换思路,实际项目中建议使用专门的库如 'gcoord' 或 'proj4'
// 将 WGS84 (GPS原始坐标) 转换为 Echarts 地图所需的投影坐标
// 注意:不同版本的 Echarts 和不同来源的 GeoJSON 对坐标要求不同,这里以常见情况为例
// 假设你有一个网点数据
const scatterData = [
{ name: '北京总部', value: [116.407526, 39.904030, 100] },
{ name: '上海分部', value: [121.473701, 31.230416, 80] },
{ name: '广州研发', value: [113.264434, 23.129162, 60] }
];
// 关键:你需要一个函数,把经纬度转换成 Echarts 地图里的像素坐标
// Echarts 提供了 ecStat 或者你可以自己写映射函数
// 但更常见的做法是:确保你的 GeoJSON 和你的数据坐标系统是匹配的
等等,上面这段话可能有点抽象。让我给你讲个真实的故事。
有一次,我的一个客户要用 Echarts 做个全国物流网点图。他把 GPS 坐标直接放进去,结果发现深圳的点跑到了越南去!为什么?因为他下载的 GeoJSON 是 GCJ-02 坐标(国内地图常用),而他的 GPS 数据是 WGS84 坐标。这两个坐标系之间有个几百米的偏移。
解决办法: 把 WGS84 坐标转换成 GCJ-02 坐标。网上有很多现成的算法(比如 google-code-prettify 上的转换算法)。
// 这是一个 WGS84 转 GCJ-02 的简化示例函数
function wgs84ToGcj02(lng, lat) {
const PI = 3.1415926535897932384626;
const a = 6378245.0; // 克拉索夫斯基椭球参数长半轴
const ee = 0.00669342162296594323; // 偏心率平方
let dLat = transformLat(lng - 105.0, lat - 35.0);
let dLng = transformLng(lng - 105.0, lat - 35.0);
const radLat = lat / 180.0 * PI;
let magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
const 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 };
}
function transformLat(x, y) {
let 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) {
let 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;
}
// 使用示例
const originalCoord = [113.264434, 23.129162]; // 广州 WGS84
const gcj02Coord = wgs84ToGcj02(originalCoord[0], originalCoord[1]);
console.log(gcj02Coord); // 转换后的 GCJ-02 坐标
方案二:使用 Echarts 的 series.map 投影参数
如果你确定你的 GeoJSON 和坐标数据是同一套体系(比如都是 WGS84),但 Echarts 默认投影不对,你可以尝试在 geo 或 series 中调整 projection。不过,这通常比较复杂,因为 Echarts 内置的投影支持有限。
我建议新手还是用方案一,先保证坐标系统一,这是最靠谱的路径。
第三步:让地图“活”起来 —— 散点图 + 流向图
坐标搞定了,现在我们要把点画上去。企业网点通常有两种展示需求:
- 散点图:每个网点是一个点,大小可以代表业务量。
- 流向图:展示从总部到各分部的物流/信息流。
让我们来实现一个综合案例:全国主要城市网点分布,圆圈大小代表业务量,颜色代表业务类型。
option = {
backgroundColor: '#0f376e', // 深色背景,更有科技感
title: {
text: '企业全国网点分布图',
subtext: '数据更新时间:2024-05-20',
left: 'center',
textStyle: { color: '#fff' }
},
tooltip: {
trigger: 'item',
formatter: function(params) {
if (params.componentType === 'series') {
return `${params.name}<br/>业务量: ${params.value[2]}`;
}
return params.name;
}
},
geo: {
map: 'china',
roam: true, // 允许缩放和平移
zoom: 1.2,
label: {
emphasis: {
show: true,
color: '#fff'
}
},
itemStyle: {
areaColor: '#323c48',
borderColor: '#404a59'
},
emphasis: {
itemStyle: {
areaColor: '#2a333d'
},
label: {
color: '#fff'
}
}
},
series: [
{
name: '网点分布',
type: 'scatter',
coordinateSystem: 'geo',
data: convertData([ // 假设 convertData 函数负责转换坐标
{ name: '北京总部', value: [116.407526, 39.904030, 100] },
{ name: '上海分部', value: [121.473701, 31.230416, 80] },
{ name: '广州研发', value: [113.264434, 23.129162, 60] },
{ name: '成都西南中心', value: [104.066541, 30.572269, 70] },
{ name: '武汉中部枢纽', value: [114.305393, 30.593099, 50] }
]),
symbolSize: function(val) {
return val[2] / 2; // 业务量越大,点越大
},
emphasis: {
focus: 'self'
}
},
{
name: '业务流向',
type: 'lines',
coordinateSystem: 'geo',
polyline: false,
lineStyle: {
color: '#a6c84c',
width: 1,
curveness: 0.2
},
effect: {
show: true,
period: 4,
trailLength: 0.1,
symbol: 'arrow',
symbolSize: 3
},
data: convertLinesData() // 从总部到各分部的线
}
]
};
// 数据转换函数
function convertData(data) {
return data.map(item => {
// 这里假设你已经通过某种方式将经纬度转换成了地图坐标
// 在实际项目中,这一步非常关键,需要根据你使用的 GeoJSON 坐标系来调整
return {
name: item.name,
value: [item.value[0], item.value[1], item.value[2]] // [lng, lat, value]
};
});
}
function convertLinesData() {
const headquarters = [116.407526, 39.904030]; // 北京
const branches = [
[121.473701, 31.230416], // 上海
[113.264434, 23.129162], // 广州
[104.066541, 30.572269], // 成都
[114.305393, 30.593099] // 武汉
];
return branches.map(b => ({
coords: [headquarters, b]
}));
}
第四步:解决“数据不显示”的终极排查清单
如果你按照上面做了,还是看不到点,别急,我们来当一回侦探,按以下步骤排查:
- 检查浏览器控制台(F12):有没有红色报错?比如 “Cannot read property ‘length’ of undefined” 或者 “地图数据未注册”。
- 检查 GeoJSON 是否加载成功:在 Network 标签里看
china.json是否 200 状态。如果加载失败,地图会是空白的。 - 检查
registerMap是否执行:在console.log里打印一下echarts.getMap('china'),如果返回undefined,说明注册失败。 - 检查坐标范围:打印出你转换后的坐标,看看它们是否在合理的范围内。如果坐标值特别大或特别小,可能是投影转换错了。
- 检查
series中的coordinateSystem:必须是'geo',不能写成'cartesian2d'。 - 检查 Echarts 版本:Echarts 4.x 和 5.x 在某些 API 上有细微差别,确保你使用的语法是当前的。
第五步:让地图更专业 —— 自定义样式和交互
企业级的地图,不能只是点个点就完事。我们需要一些“高级”功能:
- 高亮省份:鼠标悬停时,高亮显示该省份,并显示该省份下的所有网点。
- 点击跳转:点击某个网点,跳转到详情页。
- 图例和筛选:让用户可以按业务类型筛选。
// 添加鼠标悬停高亮效果
geo: {
// ... 之前的配置
roam: true,
emphasis: {
itemStyle: {
areaColor: '#2a333d',
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
},
label: {
show: true,
color: '#fff',
fontSize: 14
}
}
}
// 添加点击事件
myChart.on('click', function(params) {
if (params.componentType === 'series') {
// 点击了网点,弹出详情或跳转
alert(`你点击了 ${params.name},业务量为 ${params.value[2]}`);
} else if (params.componentType === 'geo') {
// 点击了省份,可以加载该省份的详细地图
console.log('点击了省份:', params.name);
}
});
给小朋友的比喻:地图就像拼乐高
好了,说了这么多技术细节,我用一个简单的方式来总结一下,方便你理解和记忆。
想象一下,你要用乐高积木拼一个中国地图。
GeoJSON 文件 就是乐高积木的说明书。没有说明书,你有一堆零件也不知道怎么拼。所以第一步,你要找到正确的说明书(下载 GeoJSON),并用
registerMap告诉 Echarts “这是我的说明书,名字叫 ‘china’”。经纬度坐标 就是你手里的小人偶(网点)想要站的位置。但是,乐高地基(地图)上的格子和真实世界的经纬度不是直接对应的。你需要一个“翻译官”(坐标转换算法),把你的小人偶从“真实世界坐标”搬到“乐高地图坐标”上。
