旅游神器———从基础到进阶:高德地图批量地理编码工具的迭代开发实战
·
从基础到进阶:高德地图批量地理编码工具的迭代开发实战
在日常开发中,地理编码工具是处理地址数据的常用利器。最近,我围绕高德地图API,从一个简单的地址转经纬度功能起步,逐步迭代出支持标记切换、标注避让、本地数据持久化的完整工具。这个过程中踩了不少坑,也积累了实用的开发经验,今天就结合核心代码复盘整个迭代过程。
一、初始需求:实现基础批量地理编码
核心痛点
- 单个地址解析失败导致全部标记失效;
- 批量请求无间隔,易触发接口频率限制。
解决方案与核心代码
1. 实现睡眠延迟(规避接口限制)
首先定义睡眠函数,配合async/await实现0.4秒请求间隔:
// 睡眠函数:0.4秒延迟
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
2. 重构地理编码逻辑(解决标记失效问题)
将forEach改为for循环支持异步延迟,引入计数器确保全部地址处理后再渲染:
// 地理编码核心函数(async支持await)
async function geoCode() {
// 重置状态
map.remove(markers);
markers = [];
let processedCount = 0; // 处理计数器
const addressInput = document.getElementById('address').textContent.trim();
const addresses = addressInput.split(/[,,\n]/).filter(item => item.trim() !== "");
const totalAddresses = addresses.length;
if (totalAddresses === 0) {
resultArea.innerHTML = "请输入有效的地址";
return;
}
// for循环替代forEach,支持await延迟
for (let index = 0; index < totalAddresses; index++) {
const address = addresses[index].trim();
if (!address) continue;
await sleep(400); // 每个请求间隔0.4秒
// 调用高德REST API
const url = `https://restapi.amap.com/v3/geocode/geo?address=${encodeURIComponent(address)}&output=JSON&key=${apiKey}`;
try {
const response = await fetch(url);
const data = await response.json();
if (data.status === "1" && data.count > 0) {
const location = data.geocodes[0].location.split(',').map(Number);
// 创建普通标记
const marker = new AMap.Marker({
icon: "https://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",
position: location,
title: address
});
markers.push(marker);
map.add(marker);
} else {
resultArea.innerHTML += `<span class="error">无法解析地址: ${address}</span><br>`;
}
} catch (error) {
console.error("编码出错:", error);
} finally {
processedCount++;
// 全部处理完再调整视野
if (processedCount === totalAddresses && markers.length > 0) {
map.setFitView(markers);
}
}
}
}
二、功能升级:支持标记类型自由切换
需求场景
用户需要在“普通标记(仅图标)”和“带文本标注(图标+地址名)”之间切换,且切换时地图位置不变。
核心实现代码
1. 定义标注样式与图层
// 标注图标样式
const icon = {
type: 'image',
image: "https://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",
size: [25, 34],
anchor: 'bottom-center'
};
// 标注文本样式
const textStyle = {
fontSize: 12,
fillColor: '#22886f',
strokeColor: '#fff',
strokeWidth: 2,
padding: '2, 5'
};
// 初始化标注图层(管理LabelMarker)
let labelsLayer = null;
function initLabelsLayer() {
if (!labelsLayer) {
labelsLayer = new AMap.LabelsLayer({
zooms: [3, 20],
zIndex: 1000,
allowCollision: true // 预留避让功能
});
map.add(labelsLayer);
}
}
2. 标记类型切换逻辑
// 按类型添加标记(adjustView控制是否调整视野)
function addMarkersByType(adjustView = false) {
const markerType = document.getElementById('markerType').value;
map.remove(markers); // 清除旧标记
labelsLayer?.remove(labelMarkers); // 清除旧标注
markers = [];
labelMarkers = [];
geocodeResults.forEach(result => {
if (result.success) {
if (markerType === 'marker') {
// 普通标记
const marker = new AMap.Marker({
icon: icon.image,
position: result.location,
title: result.address
});
markers.push(marker);
map.add(marker);
} else {
// 带文本标注
initLabelsLayer();
const labelMarker = new AMap.LabelMarker({
position: result.location,
icon: icon,
text: {
content: result.address,
direction: 'right',
offset: [-10, 0],
style: textStyle
}
});
labelMarkers.push(labelMarker);
labelsLayer.add(labelMarker);
}
}
});
// 仅初始加载时调整视野,切换类型时不改变位置
if (adjustView && (markers.length > 0 || labelMarkers.length > 0)) {
map.setFitView(markers.length > 0 ? markers : labelMarkers);
}
}
// 绑定切换事件
document.getElementById('markerType').onchange = () => addMarkersByType(false);
三、体验优化:添加标注避让功能
痛点解决
密集地址的文本标注易重叠,通过高德LabelsLayer的allowCollision属性实现自动避让。
核心代码
1. 避让控制按钮与逻辑
<!-- HTML按钮 -->
<div class="collision-control">
<input id="allowCollision" type="button" class="btn" value="标注避让" onclick="allowCollisionFunc()" />
<input id="notAllowCollision" type="button" class="btn disable" value="标注不避让" onclick="notAllowCollisionFunc()" />
</div>
let allowCollision = true;
// 开启避让
function allowCollisionFunc() {
allowCollision = true;
labelsLayer?.setAllowCollision(true);
toggleBtnState();
}
// 关闭避让
function notAllowCollisionFunc() {
allowCollision = false;
labelsLayer?.setAllowCollision(false);
toggleBtnState();
}
// 切换按钮禁用状态
function toggleBtnState() {
const allowBtn = document.getElementById('allowCollision');
const notAllowBtn = document.getElementById('notAllowCollision');
allowBtn.classList.toggle('disable', allowCollision);
notAllowBtn.classList.toggle('disable', !allowCollision);
}
四、实用增强:本地数据持久化存储
需求价值
避免重复编码相同地址,利用localStorage实现结果永久保存。
核心代码
1. 存储与读取逻辑
const STORAGE_KEY = "geocode_results"; // 存储键名
// 编码完成后保存结果
function saveResultsToLocal() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(geocodeResults));
updateStorageTip(); // 更新存储状态提示
}
// 读取历史数据
function loadResultsFromLocal() {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
geocodeResults = JSON.parse(stored);
addMarkersByType(true); // 加载后显示标记
resultArea.innerHTML = `<span class="success">加载历史数据${geocodeResults.length}条</span><br>`;
}
}
// 清除历史数据
function clearStoredResults() {
if (confirm("确定清除历史数据?")) {
localStorage.removeItem(STORAGE_KEY);
geocodeResults = [];
updateStorageTip();
}
}
// 页面加载时检查存储
window.onload = updateStorageTip;
function updateStorageTip() {
const tip = document.getElementById('storageTip');
const hasData = !!localStorage.getItem(STORAGE_KEY);
tip.textContent = hasData ? "已保存历史数据" : "无历史数据";
// 激活/禁用按钮
document.getElementById('loadStored').disabled = !hasData;
document.getElementById('clearStored').disabled = !hasData;
}
2. 绑定存储相关事件
<!-- HTML控制区 -->
<div class="storage-control">
<input id="loadStored" type="button" class="btn" value="加载历史数据" onclick="loadResultsFromLocal()" disabled />
<input id="clearStored" type="button" class="btn" value="清除历史数据" onclick="clearStoredResults()" disabled />
<p id="storageTip" style="font-size:0.8rem;color:#666;"></p>
</div>
五、最终工具核心特性与使用说明
功能矩阵
- 批量编码:支持逗号/换行分割多地址,0.4秒间隔请求;
- 标记切换:普通标记与带文本标注自由切换,地图位置不变;
- 标注避让:密集区域自动调整标注位置,避免重叠;
- 数据持久化:自动保存编码结果,支持历史数据复用与清除。
关键配置
- 替换代码中的
apiKey为自己的高德开放平台密钥(申请地址); - 图标URL可替换为自定义图标,调整
size属性适配尺寸; - 文本样式可通过
textStyle修改字体、颜色等参数。
总结
这个工具的迭代过程完全围绕“解决实际痛点”展开:从基础的编码功能,到优化交互体验,再到提升工具实用性。核心技术亮点在于对高德地图API的深度应用(尤其是LabelsLayer的避让特性)、异步逻辑的优雅处理(async/await替代回调),以及本地存储的灵活运用。
如果你需要开发类似工具,上述代码片段可直接复用,只需根据具体需求调整样式和交互细节即可。
更多推荐

所有评论(0)