<!-- https://unpkg.com/echarts@6.0.0/dist/echarts.min.js -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>中国地图钻取 - 带 adcode 标签</title>
<script src="https://unpkg.com/echarts@6.0.0/dist/echarts.min.js"></script>
<style>
body {
margin: 0;
padding: 20px;
font-family: "Microsoft YaHei", Arial, sans-serif;
background: #fff;
}
#backBtn {
margin-bottom: 12px;
}
#backBtn button {
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
}
#breadcrumb {
margin-bottom: 12px;
font-size: 16px;
}
#breadcrumb span {
color: #1890ff;
text-decoration: underline;
cursor: pointer;
}
#breadcrumb span:hover {
opacity: 0.8;
}
#breadcrumb span:not(:last-child)::after {
content: " > ";
color: #999;
margin: 0 6px;
}
#map {
width: 100%;
height: 600px;
border: 1px solid #eee;
border-radius: 4px;
}
</style>
</head>
<body>
<div id="backBtn">
<button onclick="goBack()">← 返回上一级</button>
</div>
<div id="breadcrumb"></div>
<div id="map"></div>
<script>
// 历史栈:记录路径 [{name, adcode}]
let historyStack = [{
name: '中国',
adcode: '100000'
}];
// 当前地图的 name -> adcode 映射(用于 label 显示)
let currentNameToAdcode = {};
let currentNameToCenter = {};
const mapContainer = document.getElementById('map');
const breadcrumbEl = document.getElementById('breadcrumb');
const backBtn = document.getElementById('backBtn');
const myChart = echarts.init(mapContainer);
// 初始加载中国地图
loadMap('100000', '中国');
// 加载指定 adcode 的地图
async function loadMap(adcode, displayName,nextCenter) {
try {
const url = `https://geo.datav.aliyun.com/areas_v3/bound/${adcode}_full.json`;
const res = await fetch(url);
if (!res.ok) throw new Error(`无法加载地图数据: ${adcode}`);
const geoJson = await res.json();
// 构建 name -> adcode 映射(用于 label 和点击)
currentNameToAdcode = {};
currentNameToCenter = {}
if (geoJson.features && Array.isArray(geoJson.features)) {
geoJson.features.forEach(feature => {
const props = feature.properties;
if (props && props.name && props.adcode !== undefined) {
currentNameToAdcode[props.name] = String(props.adcode);
currentNameToCenter[props.name] = props.center
}
});
}
// 注册地图
echarts.registerMap(displayName, geoJson);
// 渲染地图
myChart.setOption({
title: {
text: displayName,
left: 'center',
top: 10,
textStyle: {
fontSize: 18
}
},
tooltip: {
show: true,
formatter: (params) => {
const adcode = currentNameToAdcode[params.name];
return adcode ? `${params.name}<br/>adcode: ${adcode}` : params.name;
}
},
series: [{
type: 'map',
map: displayName,
center:nextCenter,
zoom: 1,
roam: true,
data: [], // 不绑定额外数据
label: {
show: true,
// 👇 关键:显示 "地名 (adcode)"
formatter: (params) => {
const adcode = currentNameToAdcode[params.name];
return adcode ? `${params.name} (${adcode})` : params.name;
},
color: '#333',
fontSize: 12
},
itemStyle: {
areaColor: '#f0f9ff',
borderColor: '#999'
},
emphasis: {
label: {
show: true,
color: '#1890ff'
},
itemStyle: {
areaColor: '#e6f7ff',
shadowBlur: 10,
shadowColor: 'rgba(0,0,0,0.5)'
}
}
}]
});
// 绑定点击事件
myChart.off('click');
myChart.on('click', async function(params) {
const regionName = params.name;
const nextAdcode = currentNameToAdcode[regionName];
const nextCenter = currentNameToCenter[regionName]
if (!nextAdcode) {
console.warn('未找到 adcode:', regionName);
return;
}
// 预检下一级是否存在(提升体验)
const testUrl = `https://geo.datav.aliyun.com/areas_v3/bound/${nextAdcode}_full.json`;
const testRes = await fetch(testUrl, {
method: 'HEAD'
});
if (testRes.ok) {
historyStack.push({
name: regionName,
adcode: nextAdcode
});
updateBreadcrumb();
loadMap(nextAdcode, regionName,nextCenter);
} else {
alert(`"${regionName}" 没有下级区域或数据不可用`);
}
});
// 更新 UI 状态
updateBreadcrumb();
updateBackButton();
} catch (err) {
console.error('加载地图失败:', err);
alert('地图加载失败,请检查网络或区域是否支持');
}
}
// 更新面包屑
function updateBreadcrumb() {
breadcrumbEl.innerHTML = '';
historyStack.forEach((item, index) => {
const span = document.createElement('span');
span.textContent = item.name;
if (index < historyStack.length - 1) {
span.onclick = () => jumpTo(index);
}
breadcrumbEl.appendChild(span);
});
}
// 跳转到指定层级
function jumpTo(index) {
const target = historyStack[index];
historyStack = historyStack.slice(0, index + 1);
loadMap(target.adcode, target.name);
}
// 返回上一级
function goBack() {
if (historyStack.length <= 1) return;
historyStack.pop();
const prev = historyStack[historyStack.length - 1];
loadMap(prev.adcode, prev.name);
}
// 控制返回按钮显示
function updateBackButton() {
backBtn.style.display = historyStack.length > 1 ? 'block' : 'none';
}
// 窗口大小变化时重绘
window.addEventListener('resize', () => myChart.resize());
</script>
</body>
</html>
这段 HTML + JavaScript 代码实现了一个 可钻取的中国行政区划地图可视化页面,使用 ECharts 库,并结合了阿里云 DataV 提供的 GeoJSON 地图数据接口。下面是对整段代码的详细解释:
🧩 整体功能概述
- 初始加载 中国全国地图。
- 用户点击某个省份(或下级区域)时,自动加载该区域的 下一级行政区划地图(如点击“广东省” → 加载广东各地市)。
- 支持 返回上一级 和 面包屑导航跳转。
- 每个区域标签显示格式为:
地名 (adcode),其中adcode是国家统计局的行政区划代码(6位数字)。 - 使用阿里云 DataV 的公开地图服务:
https://geo.datav.aliyun.com/areas_v3/bound/{adcode}_full.json
📁 文件结构与依赖
- 引入 ECharts 6.0:
<script src="https://unpkg.com/echarts@6.0.0/dist/echarts.min.js"></script> - 无其他外部依赖,纯前端实现。
🧱 页面布局(HTML + CSS)
- 返回按钮:
<div id="backBtn">,仅在非根层级显示。 - 面包屑导航:
<div id="breadcrumb">,显示当前路径(如:中国 > 广东省 > 广州市)。 - 地图容器:
<div id="map">,600px 高,响应式宽度。 - 样式简洁,强调可读性和交互反馈(如 hover 效果、下划线等)。
⚙️ 核心逻辑(JavaScript)
1. 历史栈管理
let historyStack = [{ name: '中国', adcode: '100000' }];
- 用栈记录用户浏览路径,支持回退和跳转。
100000是中国的标准 adcode。
2. 名称到 adcode 的映射
let currentNameToAdcode = {};
let currentNameToCenter = {};
- 在每次加载地图时,从 GeoJSON 的
features[].properties中提取:name→ 显示名称adcode→ 用于下钻center→ 用于地图居中(但当前未启用 zoom 自动调整)
3. loadMap(adcode, displayName, nextCenter) 函数
这是核心函数,负责:
✅ 步骤 1:请求 GeoJSON
fetch(`https://geo.datav.aliyun.com/areas_v3/bound/${adcode}_full.json`)
- 若失败(如 404),说明该区域无下级(如县级市可能没有乡镇数据)。
⚠️ 注意:你提供的错误信息
NoSuchKey表明某些 adcode 可能不存在于该接口中(例如$这种非法值),但在正常流程中不会出现。
✅ 步骤 2:构建映射表
遍历 geoJson.features,建立 name → adcode 和 name → center 的映射。
✅ 步骤 3:注册并渲染地图
echarts.registerMap(displayName, geoJson);
myChart.setOption({ ... });
- 使用
series.map类型。 label.formatter显示地名 (adcode)。tooltip也显示 adcode。- 启用
roam: true允许缩放和平移。
✅ 步骤 4:绑定点击事件
- 点击某区域 → 获取其
adcode。 - 预检:先用
HEAD请求测试下一级是否存在(避免加载失败后才提示)。 - 若存在,则压入
historyStack,调用loadMap钻取。 - 否则弹窗提示“无下级区域”。
💡 这个预检机制提升了用户体验,避免无效加载。
✅ 步骤 5:更新 UI
- 调用
updateBreadcrumb()和updateBackButton()。
4. 导航控制
🔙 goBack()
- 弹出栈顶,重新加载上一级地图。
🍞 updateBreadcrumb()
- 动态生成
<span>元素。 - 非末尾项可点击,触发
jumpTo(index)。
🎯 jumpTo(index)
- 截断历史栈到指定位置,直接跳转。
👁️ updateBackButton()
- 当栈长度 > 1 时显示“返回”按钮。
5. 响应式
window.addEventListener('resize', () => myChart.resize());
- 窗口大小变化时重绘地图。
🌐 数据源说明
- 使用阿里云 DataV 的公开地理边界服务: geo.datav.aliyun.com/areas_v3/bo…
- 支持到 区县级(部分城市到街道)。
adcode规则:- 国家:
100000 - 省份:
110000(北京)、440000(广东)等 - 城市:
440100(广州) - 区县:
440106(天河区)
- 国家:
❗注意:不是所有 adcode 都有
_full.json,比如某些直辖市辖区可能直接是叶子节点。
✅ 示例流程
- 打开页面 → 加载
100000(中国) - 点击“广东省” → 获取
adcode=440000 - 请求
440000_full.json→ 成功 → 显示广东各地市 - 点击“广州市” → 加载
440100 - 点击“天河区” → 尝试加载
440106,若无数据则提示
🔒 安全与健壮性
- 使用
try...catch捕获网络或解析错误。 - 对
fetch响应做res.ok判断。 - 预检下级是否存在,避免无效钻取。
✅ 总结
这是一个 轻量、交互性强、结构清晰 的中国地图钻取示例,非常适合用于:
- 数据大屏
- 区域分析系统
- 政务或商业 BI 展示
它充分利用了 ECharts 的地图能力和阿里云的开放地理数据,实现了从国家 → 省 → 市 → 区的逐级下钻,并带有完整的导航反馈。
如果你有具体问题(如“为什么某省点不开?”或“如何加数据热力?”),可以继续提问!