第一次接触高德地图的DistrictSearch插件时,我完全被它强大的行政区查询能力震撼到了。这个插件不仅能查询省市区的基本信息,还能获取精确的边界坐标数据。在实际项目中,我们经常需要展示从省级到区级的完整行政区划,比如疫情地图、物流配送范围规划等场景。
传统的做法是使用GeoJSON数据,但这种方式有几个明显的痛点:数据更新不及时、文件体积庞大、无法动态调整显示层级。而DistrictSearch API完美解决了这些问题,它提供的是实时、动态的行政区数据查询能力。我记得去年做一个全国物流网络项目时,客户要求能随时查看各省、各市的配送范围,用DistrictSearch只用了不到100行代码就实现了这个功能。
要使用DistrictSearch,首先需要在HTML中引入高德地图JS API和DistrictSearch插件:
html复制<script src="https://webapi.amap.com/maps?v=2.0&key=你的key&plugin=AMap.DistrictSearch"></script>
初始化地图和DistrictSearch对象的代码如下:
javascript复制const map = new AMap.Map('container', {
viewMode: '3D',
zoom: 4,
center: [116.397428, 39.90923] // 默认北京中心点
});
const district = new AMap.DistrictSearch({
extensions: 'all', // 返回边界坐标
level: 'province', // 查询级别
subdistrict: 1 // 返回下级行政区
});
这里有几个关键参数需要注意:
extensions: 'all' 必须设置才能获取边界坐标level 控制查询的行政区级别,可选值有 province/city/districtsubdistrict 设置为1表示需要下级行政区信息查询一个省份的基本信息和边界数据非常简单:
javascript复制district.search('广东省', function(status, result) {
if (status === 'complete') {
const data = result.districtList[0];
console.log('中心点坐标:', data.center);
console.log('边界数据:', data.boundaries);
console.log('下级行政区:', data.districtList);
}
});
在实际项目中,我建议把这些查询封装成Promise形式,方便后续的异步操作:
javascript复制function searchDistrict(name) {
return new Promise((resolve) => {
district.search(name, (status, result) => {
resolve(status === 'complete' ? result.districtList[0] : null);
});
});
}
要实现从省到市再到区的完整可视化,我们需要设计递归查询策略。这里分享一个我在实际项目中验证过的方案:
javascript复制async function renderFullDistrict(name, level = 'province') {
const district = new AMap.DistrictSearch({
extensions: 'all',
level,
subdistrict: 1
});
const data = await searchDistrict(name, district);
if (!data) return;
// 绘制当前级别行政区
renderPolygon(data.boundaries, getColorByLevel(level));
// 如果有下级行政区且不是最后一层
if (data.districtList && level !== 'district') {
const nextLevel = level === 'province' ? 'city' : 'district';
for (const item of data.districtList) {
await renderFullDistrict(item.name, nextLevel);
}
}
}
递归查询虽然方便,但会带来性能问题。经过多次实践,我总结了几个优化点:
优化后的代码示例:
javascript复制const districtCache = {};
async function queryWithCache(name, level) {
const cacheKey = `${name}_${level}`;
if (districtCache[cacheKey]) {
return districtCache[cacheKey];
}
const data = await searchDistrict(name, level);
districtCache[cacheKey] = data;
return data;
}
async function batchRender(names, level) {
const tasks = names.map(name => queryWithCache(name, level));
const results = await Promise.all(tasks);
// 使用requestAnimationFrame分批渲染避免卡顿
let i = 0;
function chunkRender() {
const start = i;
i += 5;
for (let j = start; j < Math.min(start + 5, results.length); j++) {
if (results[j]) {
renderPolygon(results[j].boundaries, getColorByLevel(level));
}
}
if (i < results.length) {
requestAnimationFrame(chunkRender);
}
}
chunkRender();
}
为了让多级行政区可视化更清晰,我们需要设计合理的颜色方案。我的经验是:
javascript复制function getColorByLevel(level) {
const colors = {
province: {
fill: 'rgba(23, 125, 220, 0.3)',
stroke: '#177DDC',
width: 2
},
city: {
fill: 'rgba(114, 46, 209, 0.2)',
stroke: '#722ED1',
width: 1.5
},
district: {
fill: 'rgba(19, 194, 194, 0.1)',
stroke: '#13C2C2',
width: 1
}
};
return colors[level] || colors.province;
}
function renderPolygon(paths, style) {
new AMap.Polygon({
path: paths,
fillColor: style.fill,
strokeColor: style.stroke,
strokeWeight: style.width,
map: map
});
}
好的可视化方案离不开交互设计。我们可以为行政区添加这些交互效果:
实现代码示例:
javascript复制function createInteractivePolygon(paths, properties) {
const polygon = new AMap.Polygon({
path: paths,
fillColor: properties.fill,
strokeColor: properties.stroke,
strokeWeight: properties.width,
map: map
});
// 鼠标交互效果
polygon.on('mouseover', () => {
polygon.setOptions({
fillOpacity: 0.7,
strokeWeight: properties.width * 1.5
});
});
polygon.on('mouseout', () => {
polygon.setOptions({
fillOpacity: 0.3,
strokeWeight: properties.width
});
});
polygon.on('click', () => {
map.setCenter(getPolygonCenter(paths));
map.setZoom(map.getZoom() + 2);
});
return polygon;
}
// 计算多边形中心点
function getPolygonCenter(paths) {
let lng = 0, lat = 0;
for (const point of paths[0]) {
lng += point.lng;
lat += point.lat;
}
return [lng / paths[0].length, lat / paths[0].length];
}
在实际使用中,可能会遇到某些行政区的边界数据不完整的情况。我遇到过两种典型情况:
解决方案是设置备用数据源和容错机制:
javascript复制async function getDistrictBoundary(name, level) {
try {
const data = await searchDistrict(name, level);
if (data && data.boundaries) {
return data.boundaries;
}
// 备用方案:使用上级行政区边界
if (level === 'district') {
const cityData = await searchDistrict(name, 'city');
return cityData.boundaries || [];
}
return [];
} catch (e) {
console.warn(`获取${name}边界失败`, e);
return [];
}
}
对于需要展示全国所有省市区的场景,经过多次性能测试,我总结出这套方案:
核心代码结构:
javascript复制// 在主线程
const worker = new Worker('district.worker.js');
worker.onmessage = (e) => {
const { type, data } = e.data;
if (type === 'render') {
renderCanvas(data);
}
};
map.on('viewchange', throttle(() => {
const bounds = map.getBounds();
worker.postMessage({
type: 'updateViewport',
bounds
});
}, 500));
// 在Web Worker中
class DistrictTree {
constructor(data) {
// 建立四叉树空间索引
}
queryByBounds(bounds) {
// 返回可视区域内的行政区数据
}
}
onmessage = (e) => {
if (e.data.type === 'updateViewport') {
const visibleData = districtTree.queryByBounds(e.data.bounds);
postMessage({
type: 'render',
data: visibleData
});
}
};
最后分享一个完整的省级下钻可视化实现。这个案例实现了:
javascript复制class DistrictExplorer {
constructor(map) {
this.map = map;
this.history = [];
this.currentLevel = 'province';
this.initControls();
}
initControls() {
// 添加返回按钮
const backBtn = new AMap.Control({
position: 'RB',
content: '<button id="backBtn">返回上级</button>'
});
map.addControl(backBtn);
document.getElementById('backBtn').onclick = () => this.back();
}
async loadProvince() {
this.clearMap();
this.currentLevel = 'province';
const data = await searchDistrict('中国', 'province');
this.renderProvinces(data.districtList);
}
async loadCity(provinceName) {
this.clearMap();
this.currentLevel = 'city';
const data = await searchDistrict(provinceName, 'city');
this.history.push({
name: provinceName,
level: 'province'
});
this.renderCities(data.districtList);
}
renderProvinces(provinces) {
provinces.forEach(province => {
const polygon = createInteractivePolygon(
province.boundaries,
getColorByLevel('province')
);
polygon.on('click', () => {
this.loadCity(province.name);
});
});
}
renderCities(cities) {
cities.forEach(city => {
const polygon = createInteractivePolygon(
city.boundaries,
getColorByLevel('city')
);
polygon.on('click', () => {
// 可以继续实现区级下钻
});
});
}
back() {
if (this.history.length > 0) {
const last = this.history.pop();
if (last.level === 'province') {
this.loadProvince();
}
}
}
clearMap() {
this.map.clearMap();
}
}
// 使用示例
const explorer = new DistrictExplorer(map);
explorer.loadProvince();