从基础到进阶:高德地图批量地理编码工具的迭代开发实战

在日常开发中,地理编码工具是处理地址数据的常用利器。最近,我围绕高德地图API,从一个简单的地址转经纬度功能起步,逐步迭代出支持标记切换、标注避让、本地数据持久化的完整工具。这个过程中踩了不少坑,也积累了实用的开发经验,今天就结合核心代码复盘整个迭代过程。

一、初始需求:实现基础批量地理编码

核心痛点

  1. 单个地址解析失败导致全部标记失效;
  2. 批量请求无间隔,易触发接口频率限制。

解决方案与核心代码

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);

三、体验优化:添加标注避让功能

痛点解决

密集地址的文本标注易重叠,通过高德LabelsLayerallowCollision属性实现自动避让。

核心代码

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>

五、最终工具核心特性与使用说明

功能矩阵

  1. 批量编码:支持逗号/换行分割多地址,0.4秒间隔请求;
  2. 标记切换:普通标记与带文本标注自由切换,地图位置不变;
  3. 标注避让:密集区域自动调整标注位置,避免重叠;
  4. 数据持久化:自动保存编码结果,支持历史数据复用与清除。

关键配置

  1. 替换代码中的apiKey为自己的高德开放平台密钥(申请地址);
  2. 图标URL可替换为自定义图标,调整size属性适配尺寸;
  3. 文本样式可通过textStyle修改字体、颜色等参数。

总结

这个工具的迭代过程完全围绕“解决实际痛点”展开:从基础的编码功能,到优化交互体验,再到提升工具实用性。核心技术亮点在于对高德地图API的深度应用(尤其是LabelsLayer的避让特性)、异步逻辑的优雅处理(async/await替代回调),以及本地存储的灵活运用。

如果你需要开发类似工具,上述代码片段可直接复用,只需根据具体需求调整样式和交互细节即可。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐