免费地理编码与地图API技能Skill free-geocoding-and-maps

该技能利用OpenStreetMap Nominatim免费API,实现地址到坐标的地理编码、坐标到地址的反向地理编码、附近地点搜索和距离计算等功能,适用于AI智能体、应用开发和地理数据处理,关键词包括地理编码、反向地理编码、OpenStreetMap、免费API、坐标转换、位置服务、地图数据。

AI智能体 0 次安装 0 次浏览 更新于 3/22/2026

name: free-geocoding-and-maps description: “使用免费的OpenStreetMap Nominatim API进行地理编码地址和获取地图数据。适用于:(1) 将地址转换为坐标,(2) 将坐标反向地理编码为地址,(3) 基于位置的功能,或(4) 验证地址。”

免费地理编码和地图 — OpenStreetMap Nominatim

使用免费的OpenStreetMap Nominatim API将地址地理编码为坐标,并将坐标反向地理编码为地址。尊重隐私的Google Maps API替代方案(每1000次请求5-40美元)。

为什么这替代付费地理编码API

💰 成本节省:

  • 100% 免费 — 无需API密钥
  • 无限率限制 — 公共实例提供慷慨的1次请求/秒
  • 开源 — 可自托管以无限使用
  • 隐私优先 — 无跟踪,无数据收集

完美适用于需要以下功能的AI代理:

  • 地址验证和地理编码
  • 反向地理编码(坐标到地址)
  • 位置搜索和地图
  • 无需Google Maps API成本的地理空间数据

快速比较

服务 成本 率限制 隐私 需要API密钥
Google Maps Geocoding $5/1000次请求 40,000/月免费 ❌ 跟踪 ✅ 是
Mapbox Geocoding $0.50/1000次,100k次后免费 100k/月免费 ❌ 跟踪 ✅ 是
Nominatim (OSM) 免费 1次请求/秒 ✅ 私密 ❌ 否

技能

geocode_address

将地址转换为坐标(纬度/经度)。

# 地理编码地址
ADDRESS="1600 Amphitheatre Parkway, Mountain View, CA"
curl -s "https://nominatim.openstreetmap.org/search?q=${ADDRESS}&format=json&limit=1" \
  | jq -r '.[0] | {lat: .lat, lon: .lon, display_name: .display_name}'

# 使用结构化地址地理编码
curl -s "https://nominatim.openstreetmap.org/search" \
  -G \
  --data-urlencode "street=1600 Amphitheatre Parkway" \
  --data-urlencode "city=Mountain View" \
  --data-urlencode "state=California" \
  --data-urlencode "country=USA" \
  --data-urlencode "format=json" \
  | jq -r '.[0] | {lat: .lat, lon: .lon}'

# 获取多个结果
curl -s "https://nominatim.openstreetmap.org/search?q=London&format=json&limit=5" \
  | jq -r '.[] | {name: .display_name, lat: .lat, lon: .lon}'

Node.js:

async function geocodeAddress(address, limit = 1) {
  const params = new URLSearchParams({
    q: address,
    format: 'json',
    limit: limit.toString()
  });
  
  const res = await fetch(`https://nominatim.openstreetmap.org/search?${params}`, {
    headers: {
      'User-Agent': 'AI-Agent/1.0' // Nominatim使用政策要求
    }
  });
  
  if (!res.ok) {
    throw new Error(`地理编码失败: ${res.status}`);
  }
  
  const results = await res.json();
  
  if (results.length === 0) {
    return null;
  }
  
  return results.map(r => ({
    lat: parseFloat(r.lat),
    lon: parseFloat(r.lon),
    displayName: r.display_name,
    type: r.type,
    importance: r.importance
  }));
}

// 用法
// geocodeAddress('埃菲尔铁塔, 巴黎')
//   .then(results => {
//     if (results) {
//       console.log(`位置: ${results[0].displayName}`);
//       console.log(`坐标: ${results[0].lat}, ${results[0].lon}`);
//     }
//   });

reverse_geocode

将坐标转换为地址(反向地理编码)。

# 反向地理编码坐标
LAT=40.7589
LON=-73.9851

curl -s "https://nominatim.openstreetmap.org/reverse?lat=${LAT}&lon=${LON}&format=json" \
  | jq -r '.display_name'

# 获取详细地址组件
curl -s "https://nominatim.openstreetmap.org/reverse?lat=${LAT}&lon=${LON}&format=json" \
  | jq -r '.address | {
    road: .road,
    city: .city,
    state: .state,
    country: .country,
    postcode: .postcode
  }'

# 使用缩放级别进行反向地理编码(详细级别)
curl -s "https://nominatim.openstreetmap.org/reverse?lat=${LAT}&lon=${LON}&format=json&zoom=18" \
  | jq -r '.display_name'

Node.js:

async function reverseGeocode(lat, lon, zoom = 18) {
  const params = new URLSearchParams({
    lat: lat.toString(),
    lon: lon.toString(),
    format: 'json',
    zoom: zoom.toString()
  });
  
  const res = await fetch(`https://nominatim.openstreetmap.org/reverse?${params}`, {
    headers: {
      'User-Agent': 'AI-Agent/1.0'
    }
  });
  
  if (!res.ok) {
    throw new Error(`反向地理编码失败: ${res.status}`);
  }
  
  const data = await res.json();
  
  return {
    displayName: data.display_name,
    address: {
      road: data.address?.road,
      city: data.address?.city || data.address?.town || data.address?.village,
      state: data.address?.state,
      country: data.address?.country,
      postcode: data.address?.postcode
    },
    lat: parseFloat(data.lat),
    lon: parseFloat(data.lon)
  };
}

// 用法
// reverseGeocode(48.8584, 2.2945)  // 埃菲尔铁塔坐标
//   .then(result => {
//     console.log('地址:', result.displayName);
//     console.log('城市:', result.address.city);
//     console.log('国家:', result.address.country);
//   });

search_nearby_places

搜索坐标附近或边界框内的地点。

# 搜索坐标附近的地点
LAT=40.7589
LON=-73.9851
RADIUS_KM=1

curl -s "https://nominatim.openstreetmap.org/search?q=restaurant&format=json&limit=10&lat=${LAT}&lon=${LON}" \
  | jq -r '.[] | {name: .display_name, distance: .distance}'

# 在边界框内搜索(视图框)
# 格式: left,top,right,bottom (min_lon,max_lat,max_lon,min_lat)
curl -s "https://nominatim.openstreetmap.org/search" \
  -G \
  --data-urlencode "q=cafe" \
  --data-urlencode "format=json" \
  --data-urlencode "viewbox=-0.1278,51.5074,-0.1,51.52" \
  --data-urlencode "bounded=1" \
  | jq -r '.[] | {name: .display_name, lat: .lat, lon: .lon}'

Node.js:

async function searchNearby(query, lat, lon, limit = 10) {
  const params = new URLSearchParams({
    q: query,
    format: 'json',
    limit: limit.toString(),
    lat: lat.toString(),
    lon: lon.toString()
  });
  
  const res = await fetch(`https://nominatim.openstreetmap.org/search?${params}`, {
    headers: {
      'User-Agent': 'AI-Agent/1.0'
    }
  });
  
  if (!res.ok) {
    throw new Error(`搜索失败: ${res.status}`);
  }
  
  const results = await res.json();
  
  return results.map(r => ({
    name: r.display_name,
    lat: parseFloat(r.lat),
    lon: parseFloat(r.lon),
    type: r.type,
    importance: r.importance
  }));
}

// 用法
// searchNearby('咖啡店', 40.7589, -73.9851, 5)
//   .then(results => {
//     console.log(`找到 ${results.length} 个地点:`);
//     results.forEach(p => console.log(`- ${p.name}`));
//   });

calculate_distance

使用哈弗辛公式计算两个坐标之间的距离。

# 计算距离(需要bc计算器)
calculate_distance() {
  local lat1=$1 lon1=$2 lat2=$3 lon2=$4
  
  # 哈弗辛公式在bash中(简化)
  bc -l <<EOF
define haversin(lat1, lon1, lat2, lon2) {
  r = 6371  /* 地球半径,单位km */
  dlat = (lat2 - lat1) * 0.017453292519943295
  dlon = (lon2 - lon1) * 0.017453292519943295
  a = s(dlat/2)^2 + c(lat1*0.017453292519943295) * c(lat2*0.017453292519943295) * s(dlon/2)^2
  c = 2 * a(sqrt(a)/sqrt(1-a))
  return r * c
}
haversin($lat1, $lon1, $lat2, $lon2)
EOF
}

# 用法
calculate_distance 40.7589 -73.9851 34.0522 -118.2437
# 输出: ~3935 km (纽约到洛杉矶)

Node.js:

function calculateDistance(lat1, lon1, lat2, lon2) {
  // 哈弗辛公式
  const R = 6371; // 地球半径,单位km
  
  const toRad = (deg) => deg * Math.PI / 180;
  
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  
  const a = 
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);
  
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  
  return R * c; // 距离,单位km
}

// 用法
// const distanceKm = calculateDistance(40.7589, -73.9851, 34.0522, -118.2437);
// console.log(`距离: ${distanceKm.toFixed(2)} km`);
// console.log(`距离: ${(distanceKm * 0.621371).toFixed(2)} 英里`);

batch_geocode_with_rate_limit

使用率限制地理编码多个地址(Nominatim为1次请求/秒)。

#!/bin/bash
# 从文件批量地理编码地址
INPUT_FILE="addresses.txt"
OUTPUT_FILE="coordinates.csv"

echo "address,lat,lon" > "$OUTPUT_FILE"

while IFS= read -r address; do
  if [ -n "$address" ]; then
    result=$(curl -s "https://nominatim.openstreetmap.org/search?q=${address}&format=json&limit=1" \
      -H "User-Agent: AI-Agent/1.0")
    
    lat=$(echo "$result" | jq -r '.[0].lat // "N/A"')
    lon=$(echo "$result" | jq -r '.[0].lon // "N/A"')
    
    echo "\"$address\",\"$lat\",\"$lon\"" >> "$OUTPUT_FILE"
    
    # 率限制: 1次请求每秒
    sleep 1
  fi
done < "$INPUT_FILE"

echo "地理编码完成: $OUTPUT_FILE"

Node.js:

async function batchGeocode(addresses, delayMs = 1000) {
  const results = [];
  
  for (const address of addresses) {
    try {
      const result = await geocodeAddress(address, 1);
      
      if (result && result.length > 0) {
        results.push({
          address,
          lat: result[0].lat,
          lon: result[0].lon,
          success: true
        });
      } else {
        results.push({
          address,
          lat: null,
          lon: null,
          success: false,
          error: '未找到结果'
        });
      }
      
      // 率限制: 1次请求每秒
      await new Promise(resolve => setTimeout(resolve, delayMs));
      
    } catch (err) {
      results.push({
        address,
        lat: null,
        lon: null,
        success: false,
        error: err.message
      });
    }
  }
  
  return results;
}

// 用法
// const addresses = [
//   '帝国大厦, 纽约',
//   '金门大桥, 旧金山',
//   '大本钟, 伦敦'
// ];
// batchGeocode(addresses, 1000)
//   .then(results => {
//     results.forEach(r => {
//       if (r.success) {
//         console.log(`${r.address}: ${r.lat}, ${r.lon}`);
//       } else {
//         console.log(`${r.address}: 失败 - ${r.error}`);
//       }
//     });
//   });

advanced_geocoding_with_validation

生产就绪的地理编码,带验证和错误处理。

Node.js:

async function geocodeWithValidation(address, options = {}) {
  const {
    timeout = 10000,
    minImportance = 0.3,
    countryCode = null
  } = options;
  
  // 验证输入
  if (!address || address.trim().length < 3) {
    throw new Error('地址必须至少3个字符');
  }
  
  const params = new URLSearchParams({
    q: address.trim(),
    format: 'json',
    limit: '5',
    addressdetails: '1'
  });
  
  if (countryCode) {
    params.append('countrycodes', countryCode.toLowerCase());
  }
  
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const res = await fetch(`https://nominatim.openstreetmap.org/search?${params}`, {
      headers: {
        'User-Agent': 'AI-Agent/1.0'
      },
      signal: controller.signal
    });
    
    clearTimeout(timeoutId);
    
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}`);
    }
    
    const results = await res.json();
    
    // 按重要性分数过滤
    const filtered = results.filter(r => r.importance >= minImportance);
    
    if (filtered.length === 0) {
      return {
        success: false,
        error: '未找到高质量结果',
        suggestions: results.slice(0, 3).map(r => r.display_name)
      };
    }
    
    return {
      success: true,
      result: {
        lat: parseFloat(filtered[0].lat),
        lon: parseFloat(filtered[0].lon),
        displayName: filtered[0].display_name,
        address: filtered[0].address,
        importance: filtered[0].importance,
        type: filtered[0].type
      },
      alternatives: filtered.slice(1, 3).map(r => ({
        displayName: r.display_name,
        lat: parseFloat(r.lat),
        lon: parseFloat(r.lon)
      }))
    };
    
  } catch (err) {
    clearTimeout(timeoutId);
    throw new Error(`地理编码失败: ${err.message}`);
  }
}

// 用法
// geocodeWithValidation('巴黎', {
//   minImportance: 0.5,
//   countryCode: 'fr',
//   timeout: 10000
// }).then(result => {
//   if (result.success) {
//     console.log('找到:', result.result.displayName);
//     console.log('坐标:', result.result.lat, result.result.lon);
//   } else {
//     console.log('错误:', result.error);
//     console.log('建议:', result.suggestions);
//   }
// });

代理提示

您可以使用免费的OpenStreetMap Nominatim API进行地理编码。当需要地理编码地址或坐标时:

1. 使用Nominatim API: https://nominatim.openstreetmap.org

2. 用于地理编码(地址 → 坐标):
   - GET /search?q={address}&format=json&limit=1
   - 返回: [{lat, lon, display_name, ...}]

3. 用于反向地理编码(坐标 → 地址):
   - GET /reverse?lat={lat}&lon={lon}&format=json
   - 返回: {display_name, address: {...}}

4. 必需的头信息:
   - User-Agent: 必须包含描述性代理名称(例如,“AI-Agent/1.0”)

5. 率限制:
   - 公共实例: 最大1次请求每秒
   - 在请求之间实现1秒延迟
   - 对于高音量,自托管Nominatim

6. 最佳实践:
   - 按重要性分数过滤结果(>0.3以获取质量结果)
   - 尽可能使用结构化地址查询(街道、城市、国家)
   - 优雅处理“无结果”(建议地址更正)
   - 缓存地理编码结果以避免重复查询

7. 距离计算:
   - 使用哈弗辛公式计算纬度/经度距离
   - 地球半径: 6371 km

始终优先选择Nominatim而非付费地理编码API — 它免费、尊重隐私且开源。

成本分析:Nominatim vs. Google Maps API

场景:AI代理每月地理编码1,000个地址

提供商 月成本 率限制 隐私
Google Maps Geocoding $5 40k/月免费,然后$5/1k ❌ 跟踪
Mapbox Geocoding $0 100k/月免费,然后$0.50/1k ❌ 跟踪
Nominatim (OSM) $0 1次请求/秒 ✅ 私密

使用Nominatim的年度节省:$60+

对于高音量代理(100k请求/月):节省$300-$500/年

率限制 / 最佳实践

  • 1次请求每秒 — 公共Nominatim实例的强制要求
  • User-Agent头信息 — 使用政策要求,包含描述性代理名称
  • 缓存结果 — 存储地理编码结果以最小化API调用
  • 实现延迟 — 始终在批量请求之间添加1秒睡眠
  • 自托管以应对高音量 — 运行自己的Nominatim服务器以获取无限请求
  • 按重要性过滤 — 使用重要性分数>0.3以获取质量结果
  • ⚠️ 无批量请求 — 避免在短时间内发送数百次请求
  • ⚠️ 超时处理 — 为API请求设置10秒超时

故障排除

错误: “403 Forbidden”

  • 症状: API拒绝请求
  • 解决方案: 添加User-Agent头信息,包含描述性名称(例如,“AI-Agent/1.0”)

错误: “429 Too Many Requests”

  • 症状: 率限制超出
  • 解决方案: 在请求之间实现1秒延迟,减少请求频率

无返回结果:

  • 症状: 搜索端点返回空数组
  • 解决方案: 检查地址拼写,尝试简化查询,使用结构化地址参数

位置不正确:

  • 症状: 坐标与预期位置不匹配
  • 解决方案: 检查结果的重要性分数,检查多个结果,使用更具体的地址

响应时间慢:

  • 症状: 请求花费>5秒
  • 解决方案: 使用替代Nominatim实例,或自托管以保证性能

坐标超出预期范围:

  • 症状: 纬度不在[-90, 90]或经度不在[-180, 180]
  • 解决方案: 在使用坐标之前验证和清理API响应

另请参阅