说实话,第一次搞自定义地图的时候,我整个人都是懵的。明明数据有,坐标也对,但地图上就是显示不出来,或者数据全跑到地图外面去了。折腾了一周多,头发掉了一把,终于把这套流程彻底搞清楚了。今天不想给你整那些虚头巴脑的理论,咱们直接开干,把这里面所有的坑都给你填平。
为什么非得用 GeoJSON?
先别急着复制粘贴代码,你得先明白我们在干什么。Echarts 自带的地图其实就那么几个:中国、世界、还有各省的。但如果你做的是社区大屏、企业内部系统,或者某个特定园区、特定楼层的可视化,这些现成的地图根本不够用。
GeoJSON 就是个标准格式,它把地图的几何形状(点、线、面)和属性信息打包在一起。你可以用 ArcGIS 画出来,可以用 QGIS 导出,甚至可以自己在在线工具上描一遍。拿到这个文件,Echarts 就能把它渲染成一张图,而且最重要的是——这是你自己的数据,你想画啥画啥。
第一步:准备你的 GeoJSON 文件
这是基础中的基础。我拿一个虚拟的“阳光科技园”地图来做例子。你可以从网上找一个现成的,比如在高德地图开放平台或者 GeoJSON.io 上找。
假设你已经有了 sunshine_park.json,里面的数据结构大概长这样:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "A栋办公楼",
"id": "A01"
},
"geometry": {
"type": "Polygon",
"coordinates": [[[121.5, 31.2], [121.6, 31.2], [121.6, 31.3], [121.5, 31.3], [121.5, 31.2]]]
}
},
{
"type": "Feature",
"properties": {
"name": "B栋研发中心",
"id": "B01"
},
"geometry": {
"type": "Polygon",
"coordinates": [[[121.6, 31.2], [121.7, 31.2], [121.7, 31.3], [121.6, 31.3], [121.6, 31.2]]]
}
}
]
}
注意看 properties 里的那个 id,这个非常关键,后面绑定数据全靠它。如果你的是中文 name,也可以,但建议用 ID 做关联,因为中文有时候会有空格或者特殊字符,容易对不上。
第二步:注册地图到 Echarts
这步最简单,但也最容易出错。很多人直接把 JSON 字符串扔进去,结果报错。正确的做法是:
- 先注册:用
echarts.registerMap方法,给地图起个名字,比如叫sunshine_park。 - 后引用:在配置项
series里,通过map: 'sunshine_park'来调用它。
// 假设你已经有了 geoJsonData 这个对象
// 可能是从 fetch 或者 axios 拿回来的,也可能是直接 require 进来的
echarts.registerMap('sunshine_park', geoJsonData);
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option = {
tooltip: {
trigger: 'item',
formatter: '{b}' // b 代表 properties.name
},
series: [{
type: 'map',
map: 'sunshine_park', // 这里要跟 registerMap 的第一个参数一致
roam: true, // 允许缩放和平移
zoom: 1.2,
label: {
show: true,
color: '#333'
},
// 样式设置
itemStyle: {
areaColor: '#eee',
borderColor: '#999'
},
emphasis: {
itemStyle: {
areaColor: '#ffb347'
},
label: {
color: '#000'
}
}
}]
};
myChart.setOption(option);
跑起来之后,你应该能看到两个方块,分别是 A 栋和 B 栋。如果看不到,或者报了“地图不存在”的错,那一定是 registerMap 和 map 的名字对不上,或者是异步加载的时候时序出了问题——记得先 register 再 init 或 setOption。
第三步:解决坐标偏移——这是最大的坑
到这里,90% 的人都会卡住。为什么?因为 Echarts 官方文档里没说清楚,但实际开发中,GeoJSON 的坐标系和 Echarts 默认的坐标系可能不匹配。
国内常用的坐标系有三种:
- WGS84:GPS 原始坐标,国际通用。GeoJSON 标准默认就是这个。
- GCJ02:高德地图、腾讯地图用的,俗称“火星坐标系”。
- BD09:百度地图用的。
如果你的 GeoJSON 是 WGS84,而你想把它叠在高德底图上,或者你的业务系统内部用的是 GCJ02,直接画上去,位置会偏几千甚至几万公里!
怎么判断和转换?
我建议你用 proj4 或者在线工具先测试一下。但更常见的情况是,你拿到手的数据就是 GCJ02 的(比如从高德导出),而 Echarts 解析 GeoJSON 时默认认为它是 WGS84。这时候数据就会偏移。
解决方案:使用转换工具库
有一个很成熟的库叫 coordtransform,专门做这个坐标转换的。
npm install coordtransform --save
然后在代码里引入并转换:
import coordtransform from 'coordtransform';
function transformCoords(geojson) {
// 深度克隆,避免修改原数据
const clonedGeojson = JSON.parse(JSON.stringify(geojson));
// 遍历所有 features
clonedGeojson.features.forEach(feature => {
const coords = feature.geometry.coordinates;
// 递归处理坐标点
const transformCoordinate = (coord) => {
return coordtransform.gcj02ToWgs84(coord[0], coord[1]);
};
// 根据 geometry 类型处理
if (feature.geometry.type === 'Point') {
feature.geometry.coordinates = transformCoordinate(coords);
} else if (feature.geometry.type === 'LineString') {
feature.geometry.coordinates = coords.map(transformCoordinate);
} else if (feature.geometry.type === 'Polygon' || feature.geometry.type === 'MultiPolygon') {
// Polygon 是数组的数组...处理起来比较麻烦,需要递归
const transformPolygon = (polygon) => {
return polygon.map(ring => ring.map(coordtransform.gcj02ToWgs84));
};
// 这里简化处理,实际可能需要根据层级递归
if (Array.isArray(coords[0][0][0])) {
// MultiPolygon
feature.geometry.coordinates = coords.map(transformPolygon);
} else {
// Polygon
feature.geometry.coordinates = transformPolygon(coords);
}
}
});
return clonedGeojson;
}
// 使用:先把 GCJ02 的 GeoJSON 转成 WGS84,再注册
const transformedGeo = transformCoords(geoJsonData);
echarts.registerMap('sunshine_park', transformedGeo);
注意:如果你的数据本来就是 WGS84,而你要叠的是 WGS84 的底图,那就不用转。如果数据来源不明,先用高德地图 API 查一个坐标,对比一下 GeoJSON 里的坐标,看看偏没偏。
第四步:数据绑定——让地图“活”起来
地图显示出来了,位置也对了,现在要给每个楼栋显示数据,比如“当前人数”、“能耗指数”这种。这就是数据绑定。
Echarts 的数据绑定主要靠 data 数组和 properties 里的 id 或 name 进行关联。
假设我们有这样的业务数据:
const businessData = [
{ id: 'A01', value: 120, status: 'normal' },
{ id: 'B01', value: 45, status: 'warning' }
];
我们需要把这个数据和 GeoJSON 里的 properties.id 对应起来。
// 1. 构建一个 Map,方便快速查找
const dataMap = new Map();
businessData.forEach(item => {
dataMap.set(item.id, item);
});
// 2. 处理 GeoJSON 数据,把业务数据挂载到 properties 上
// 这一步非常关键!很多教程跳过这步,导致后面拿不到数据
const processedFeatures = geoJsonData.features.map(feature => {
const id = feature.properties.id;
const bizData = dataMap.get(id);
if (bizData) {
// 把业务数据合并进去,方便后续 formatter 使用
feature.properties = { ...feature.properties, ...bizData };
}
return feature;
});
// 3. 重新构建 GeoJSON 对象(因为 features 被修改了)
const mergedGeoJson = { ...geoJsonData, features: processedFeatures };
// 4. 重新注册(或者用 update)
echarts.registerMap('sunshine_park', mergedGeoJson);
现在,地图里的每一个块,都带着自己的 value 和 status 了。接下来就是在图表配置里把它们展示出来。
第五步:可视化呈现——颜色和提示框
数据绑定了,怎么显示呢?通常有两种方式:色阶映射和散点标记。
方式一:动态变色(推荐)
根据 value 的大小,给不同的楼栋涂上不同的颜色。
series: [{
type: 'map',
map: 'sunshine_park',
roam: true,
data: businessData, // 直接传业务数据数组,Echarts 会自动根据 id 匹配
visualMap: {
min: 0,
max: 200,
left: 'left',
top: 'bottom',
text: ['高', '低'],
calculable: true,
inRange: {
color: ['#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027']
},
textStyle: { color: '#333' }
},
label: {
show: true,
formatter: '{b}: {c}' // b 是 name, c 是 value
},
emphasis: {
disabled: true // 禁用 hover 时的放大效果,避免干扰
}
}]
方式二:自定义 Tooltip(更炫酷)
默认的 tooltip 只显示 name 和 value 有点单调。我们可以自定义 HTML 内容,甚至加入图标、进度条。
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#ccc',
borderWidth: 1,
textStyle: { color: '#333' },
formatter: function(params) {
// params 是当前点击的 map 数据,里面有 properties
const props = params.data;
if (!props) return params.name;
// 根据 status 显示不同的状态标签
let statusHtml = '';
if (props.status === 'warning') {
statusHtml = '<span style="color:red; margin-left:5px;">⚠️ 预警</span>';
} else if (props.status === 'normal') {
statusHtml = '<span style="color:green; margin-left:5px;">✓ 正常</span>';
}
return `
<div style="padding: 10px;">
<h4 style="margin:0 0 5px; font-size:14px;">${props.name}</h4>
<p style="margin:0; color:#666;">当前人数:<strong style="font-size:16px;">${props.value}</strong> 人</p>
<p style="margin:5px 0 0;">状态:${statusHtml}</p>
</div>
`;
}
}
第六步:交互进阶——点击跳转、弹窗详情
很多业务需求是:点击某个楼栋,弹出一个侧边栏或者新页面,展示更详细的数据。这时候需要监听 click 事件。
myChart.on('click', function(params) {
console.log('点击了:', params);
// params.event.componentType === 'series'
// params.name 是 properties.name
// params.value 是 data 数组里的 value
// 假设你有一个弹窗组件
openDetailModal({
title: params.name,
id: params.data.id,
currentPeople: params.data.value,
// 你可以从 params.data 里拿所有挂在 properties 上的数据
extraInfo: params.data.someOtherField
});
});
常见问题排查清单(血泪总结)
地图不显示,报
Uncaught Error: The geoJson of ... is not exists- 检查
registerMap的名字和series.map的名字是否完全一致,包括大小写。 - 检查 GeoJSON 数据是否成功加载,有没有跨域问题(CORS)。
- 如果是异步加载,确保
registerMap在setOption之前执行。
- 检查
数据绑不上,tooltip 里 value 是 undefined
- 检查 GeoJSON 里的
properties.id和业务数据里的id是否完全匹配(注意空格、大小写)。 - 检查
series.data是否正确传入,或者是否在registerMap前已经合并到了 properties 里。
- 检查 GeoJSON 里的
地图位置和实际对不上,偏了好远
- 99% 是坐标系问题。确认 GeoJSON 的坐标系,用
coordtransform库进行转换。 - 如果 GeoJSON 是 GCJ02,而你想展示在 WGS84 的基准上(或者反过来),务必转换。
- 99% 是坐标系问题。确认 GeoJSON 的坐标系,用
小地图在放大后变形或者模糊
- GeoJSON 精度问题。如果地图很复杂,点太多,渲染会卡。考虑简化 GeoJSON(用 TopoJSON 或者简化算法减少点数)。
点击事件失效
- 检查
roam: true是否开启,有时候缩放平移会改变地图的坐标系,导致点击坐标计算错误。 - 检查是否有其他 DOM 元素遮挡了地图 canvas。
- 检查
结语
搞自定义地图,其实就是一句话:数据要准,坐标要对,绑定要明。
一开始我也觉得这个 GeoJSON 格式复杂得要命,但当你真正理解了 Feature、Properties、Geometry 这三层结构的关系,再配合上 Echarts 的 registerMap 和 data 绑定机制,你会发现这事儿其实挺有成就感的。
别怕报错,把控制台的红字仔细看一遍,大部分时候错误信息已经告诉你缺什么了。希望这篇文章能帮你省下那几天掉头发的时间,早点下班!如果有具体的 GeoJSON 数据不知道怎么处理,随时把片段发出来,咱们一起看。
