html 地图下钻

20 阅读5分钟
<!-- 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 → adcodename → 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,比如某些直辖市辖区可能直接是叶子节点。


✅ 示例流程

  1. 打开页面 → 加载 100000(中国)
  2. 点击“广东省” → 获取 adcode=440000
  3. 请求 440000_full.json → 成功 → 显示广东各地市
  4. 点击“广州市” → 加载 440100
  5. 点击“天河区” → 尝试加载 440106,若无数据则提示

🔒 安全与健壮性

  • 使用 try...catch 捕获网络或解析错误。
  • fetch 响应做 res.ok 判断。
  • 预检下级是否存在,避免无效钻取。

✅ 总结

这是一个 轻量、交互性强、结构清晰 的中国地图钻取示例,非常适合用于:

  • 数据大屏
  • 区域分析系统
  • 政务或商业 BI 展示

它充分利用了 ECharts 的地图能力和阿里云的开放地理数据,实现了从国家 → 省 → 市 → 区的逐级下钻,并带有完整的导航反馈。

如果你有具体问题(如“为什么某省点不开?”或“如何加数据热力?”),可以继续提问!