说到地图可视化,很多人第一反应就是“这得有多难”,毕竟以前做地图确实需要懂GIS、懂投影、懂投影坐标转换,光是下载一个全国的GeoJSON文件就能让电脑卡半天。但自从ECharts出了registerMap这个方法,一切都变得简单多了。我今天就想和你聊聊,怎么用最轻松的方式,画出一张精准的县级省市地图。
先别急着写代码,咱们聊聊核心概念
在你打开编辑器之前,我得先让你明白一个事儿:ECharts里的地图,本质上就是一个SVG路径的集合。
不管是你看到的“某某省地图”还是“某某市地图”,在计算机眼里,它都是一堆<path>标签拼起来的。这些路径描述了每个行政区域的边界坐标。ECharts提供了一个强大的接口叫registerMap,它允许你把自己定义的任何地图“注册”进去,然后你就可以用geo组件或者series-map去渲染它。
这里有两个关键点你必须记住:
- 坐标系问题:ECharts默认使用的是地理坐标系(Geographic Coordinate System),它把经度(lng)和纬度(lat)直接映射到屏幕坐标上。你不需要自己算投影,ECharts帮你搞定了。
- 数据格式问题:注册地图时,你通常需要提供两种东西——几何数据(也就是地图长什么样)和属性数据(也就是每个区域叫什么名字、ID是多少)。
第一步:获取地图数据——最让人头疼但其实最简单的环节
很多新手在这里就放弃了,因为感觉“我去哪儿找地图数据啊?”。
其实,现在有几个非常成熟的渠道:
1. 使用阿里云DataV.GeoAtlas(推荐新手)
这是目前国内最方便的地图数据源之一。你只需要访问 https://datav.aliyun.com/portal/school/atlas/area_selector,然后你就可以下载任何你想要的区域。
- 支持从全国到街道级的所有层级
- 提供JSON格式和SVG格式两种导出方式
- JSON格式可以直接被ECharts使用
- SVG格式适合做自定义样式或者二次加工
2. 使用ECharts官方示例数据
如果你只是想快速跑通一个Demo,ECharts自带的world.js、china.js就够用了。但这些是预编译的JS文件,包含的是简化的边界数据,精度没那么高。
3. 自己动手提取GeoJSON
如果你需要特定区域的定制数据,可以从Natural Earth或者GADM下载原始GeoJSON,然后用一些工具(比如geojson.io)进行简化或裁剪。
第二步:理解SVG注册——为什么我要用SVG?
你可能会问:“直接给我JSON不就行了吗,为什么还要讲SVG?”
这是因为SVG提供了更强大的控制能力。想象一下,如果你只想高亮某个特定区域,或者给某个区域的边界加上特殊的描边样式,SVG的方式会让你更自由。
SVG注册地图的基本原理
当你通过阿里云下载SVG格式的地图时,你得到的是一个标准的SVG文件。里面的路径数据长这样:
<path
d="M 120.5,30.2 L 120.6,30.2 L 120.6,30.3 L 120.5,30.3 Z"
fill="#ffffff"
stroke="#999999"
stroke-width="1"
data-name="西湖区"
data-id="330106"
/>
注意看这几个属性:
d:路径的绘制指令,描述了这个区域的边界data-name:区域的名称(比如“西湖区”)data-id:区域的唯一标识符(比如行政区划代码)
如何把SVG转换成ECharts可用的数据
ECharts的registerMap其实接受的是JSON对象,不是直接接受SVG文件。所以我们需要一个转换过程。
这里有一个非常实用的JavaScript工具函数,它可以解析SVG并转换成ECharts需要的格式:
/**
* 将SVG字符串解析并转换为ECharts registerMap可用的GeoJSON格式
* @param {string} svgString - SVG文件内容
* @param {string} defaultRegionName - 默认区域名称(用于没有name属性的path)
*/
function svgToGeoJSON(svgString, defaultRegionName = 'Unknown') {
// 创建一个临时的DOM解析器
const parser = new DOMParser();
const doc = parser.parseFromString(svgString, 'image/svg+xml');
const features = [];
const paths = doc.querySelectorAll('path');
paths.forEach((path, index) => {
// 提取路径数据
const d = path.getAttribute('d');
// 提取区域名称(优先用data-name,其次用title,最后用默认值)
const name = path.getAttribute('data-name') ||
path.getAttribute('title') ||
path.getAttribute('name') ||
`${defaultRegionName}_${index}`;
// 提取区域ID
const id = path.getAttribute('data-id') ||
path.getAttribute('id') ||
`region_${index}`;
// 提取颜色(如果有)
const fillColor = path.getAttribute('fill') || '#ffffff';
// 将SVG路径转换为GeoJSON的Polygon格式
// 注意:这里是一个简化版,实际项目中可能需要更复杂的坐标转换
const coordinates = parseSVGPathToCoordinates(d);
features.push({
type: 'Feature',
properties: {
name: name,
id: id,
fillColor: fillColor
},
geometry: {
type: 'Polygon',
coordinates: [coordinates] // 注意:GeoJSON的坐标是[LNG, LAT]顺序
}
});
});
return {
type: 'FeatureCollection',
features: features
};
}
/**
* 简单解析SVG路径数据为坐标数组
* 这是一个简化实现,实际项目中建议使用d3.geo.path或paperlib等库
*/
function parseSVGPathToCoordinates(d) {
// 这里只是一个示例,实际解析SVG路径非常复杂
// 建议直接使用现有的库如:
// - @antv/data-set 的 GeoJSON 转换
// - d3-dsv 配合 d3-geo
// - 或者直接使用在线转换工具
// 简化示例:假设d已经是坐标对格式
const matches = d.match(/[MLZ]\s*([\d\.]+)\s*,\s*([\d\.]+)/gi);
if (!matches) return [];
return matches.map(match => {
const coords = match.match(/[\d\.]+/g);
return [parseFloat(coords[0]), parseFloat(coords[1])];
});
}
等等,我刚才说的那个parseSVGPathToCoordinates函数其实太简化了,真实项目中千万别这么用。SVG路径解析是非常复杂的数学问题,涉及到贝塞尔曲线、二次贝塞尔曲线等等。
所以我的建议是:不要在JS里自己解析SVG。直接用工具转换。
第三步:使用工具进行格式转换——这是最关键的一步
我刚刚说的那个小函数只是教学演示,真正干活的时候,我们有更好的选择。
方案A:使用在线转换工具(最快)
访问 geojson.io 或者 MapShaper,把你下载的SVG文件上传,选择导出为GeoJSON。然后你就可以直接在ECharts里使用了。
方案B:使用Python脚本批量转换
如果你需要处理大量地图数据,用Python会更高效:
import geojson
from shapely import wkt
from shapely.geometry import shape, mapping
import svgpathtools
import re
def svg_path_to_geojson(svg_file_path, output_geojson_path):
"""
将SVG文件转换为GeoJSON格式
"""
# 读取SVG文件
with open(svg_file_path, 'r', encoding='utf-8') as f:
svg_content = f.read()
# 使用svgpathtools解析路径
# 注意:这只是一个简化示例,实际处理需要考虑投影和坐标系统
# 更简单的方法:使用d3-geo-projection或直接使用在线API
# 这里建议使用现成的库如:
# - geojsonio 的API
# - 或者直接使用阿里云DataV导出的JSON(已经是GeoJSON格式)
print(f"建议直接使用阿里云DataV导出的JSON格式,比手动转换更可靠")
return None
# 实际使用中,我们更推荐:
# 1. 从阿里云下载JSON格式的地图数据
# 2. 直接使用,无需转换
说实话,写到这儿我得承认:绝大多数情况下,你根本不需要自己转换格式。阿里云DataV导出的JSON就是标准的GeoJSON,直接就能用。除非你有特殊需求(比如要把SVG做成动画、或者要叠加其他矢量图层),否则跳过转换这一步,直接用JSON。
方案C:直接使用ECharts内置的地图数据
如果你只是想快速出图,ECharts官方提供了很多预置地图。你只需要引入对应的JS文件:
<!-- 引入ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<!-- 引入中国地图数据 -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/map/js/china.js"></script>
<script>
// 注册中国地图
echarts.registerMap('china', window.chinaGeoJSON);
// 然后就可以正常使用了
const chart = echarts.init(document.getElementById('main'));
chart.setOption({
geo: {
map: 'china',
roam: true,
zoom: 1.2
},
series: [{
type: 'map',
geoIndex: 0,
data: [
{name: '北京市', value: 100},
{name: '上海市', value: 200}
]
}]
});
</script>
看,是不是很简单?但这里有个坑:window.chinaGeoJSON这个变量名是ECharts官方文档里规定的,不同版本的ECharts可能不一样。建议你去看一下你下载的JS文件里具体暴露了什么全局变量。
第四步:完整实战——绘制一个县级地图
好了,理论知识讲得差不多了,咱们来点实际的。假设你已经从阿里云下载了一个“杭州市”的JSON文件(包含下辖区县的数据),现在我要画一张杭州市的地图,并且每个区县有不同的颜色表示数据大小。
文件准备
首先,确保你有以下文件:
hangzhou.json- 杭州市的GeoJSON数据data.json- 每个区县的数据(名称和数值)
hangzhou.json的结构大致如下:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "上城区",
"adcode": "330102"
},
"geometry": {
"type": "Polygon",
"coordinates": [[[120.1, 30.2], [120.2, 30.2], [120.2, 30.3], [120.1, 30.3], [120.1, 30.2]]]
}
},
{
"type": "Feature",
"properties": {
"name": "下城区",
"adcode": "330103"
},
"geometry": {
"type": "Polygon",
"coordinates": [[[120.2, 30.2], [120.3, 30.2], [120.3, 30.3], [120.2, 30.3], [120.2, 30.2]]]
}
}
// ... 其他区县
]
}
完整代码实现
”`html <!DOCTYPE html>
杭州市各区县GDP分布图
<div class="info-item">
<div class="color-box" style="background: #4472C4;"></div>
<span>高GDP区域 (>8000亿)</span>
</div>
<div class="info-item">
<div class="color-box" style="background: #ED7D31;"></div>
<span>中等GDP区域 (4000-8000亿)</span>
</div>
<div class="info-item">
<div class="color-box" style="background: #A5A5A5;"></div>
<span>低GDP区域 (<4000亿)</span>
</div>
// 模拟的地图数据(实际项目中应该从文件加载)
const hangzhouGeoJSON = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { "name": "上城区", "adcode": "330102" },
"geometry": { "type": "Polygon", "coordinates": [[[120.15, 30.20], [120.25, 30.20], [120.25, 30.28], [120.15, 30.28], [120.15, 30.20]]] }
},
{
"type": "Feature",
"properties": { "name": "下城区", "adcode": "330103" },
"geometry": { "type": "Polygon", "coordinates": [[[120.25, 30.20], [120.35, 30.20], [120.35, 30.28], [120.25, 30.28], [120.25, 30.20]]] }
},
{
"type": "Feature",
"properties": { "name": "江干区", "adcode": "330104" },
"geometry": { "type": "Polygon", "coordinates": [[[120.35, 30.20], [120.45, 30.20], [120.45, 30.28], [120.35, 30.28], [120.35, 30.20]]] }
},
{
"type": "Feature",
"properties": { "name": "拱墅区", "adcode": "330105" },
"geometry": { "type": "Polygon", "coordinates": [[[120.15, 30.28], [120.35, 30.28], [120.35, 30.36], [120.15, 30.36], [120.15, 30.28]]] }
},
{
"type": "Feature",
"properties": { "name": "西湖区", "
