free-geocoding-and-maps
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFree Geocoding and Maps — OpenStreetMap Nominatim
免费地理编码与地图服务 — OpenStreetMap Nominatim
Geocode addresses to coordinates and reverse geocode coordinates to addresses using free OpenStreetMap Nominatim API. Privacy-respecting alternative to Google Maps API ($5-40/1000 requests).
使用免费的OpenStreetMap Nominatim API对地址进行地理编码(地址转坐标)和反向地理编码(坐标转地址)。这是尊重隐私的Google Maps API替代方案(Google Maps API每1000次请求费用为5-40美元)。
Why This Replaces Paid Geocoding APIs
为何选择它替代付费地理编码API
💰 Cost savings:
- ✅ 100% free — no API keys required
- ✅ No rate limits — generous 1 request/second for public instances
- ✅ Open source — self-hostable for unlimited usage
- ✅ Privacy-first — no tracking, no data collection
Perfect for AI agents that need:
- Address validation and geocoding
- Reverse geocoding (coordinates to address)
- Location search and mapping
- Geospatial data without Google Maps API costs
💰 成本节省:
- ✅ 100%免费 — 无需API密钥
- ✅ 无请求速率限制上限 — 公共实例默认支持每秒1次请求
- ✅ 开源 — 可自行部署以实现无限制使用
- ✅ 隐私优先 — 无追踪、无数据收集
非常适合有以下需求的AI Agent:
- 地址验证与地理编码
- 反向地理编码(坐标转地址)
- 位置搜索与地图开发
- 无需承担Google Maps API成本的地理空间数据处理
Quick comparison
快速对比
| Service | Cost | Rate limit | Privacy | API key required |
|---|---|---|---|---|
| Google Maps Geocoding | $5/1000 requests | 40,000/month free | ❌ Tracked | ✅ Yes |
| Mapbox Geocoding | $0.50/1000 after 100k free | 100k/month free | ❌ Tracked | ✅ Yes |
| Nominatim (OSM) | Free | 1 req/sec | ✅ Private | ❌ No |
| 服务 | 成本 | 请求速率限制 | 隐私保护 | 是否需要API密钥 |
|---|---|---|---|---|
| Google Maps Geocoding | 每1000次请求5美元 | 每月免费40000次 | ❌ 会追踪数据 | ✅ 是 |
| Mapbox Geocoding | 10万次免费请求后,每1000次0.5美元 | 每月免费10万次 | ❌ 会追踪数据 | ✅ 是 |
| Nominatim (OSM) | 免费 | 每秒1次 | ✅ 隐私保护 | ❌ 否 |
Skills
功能技能
geocode_address
geocode_address
Convert address to coordinates (latitude/longitude).
bash
undefined将地址转换为坐标(纬度/经度)。
bash
undefinedGeocode an address
Geocode an 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}'
| jq -r '.[0] | {lat: .lat, lon: .lon, display_name: .display_name}'
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}'
| jq -r '.[0] | {lat: .lat, lon: .lon, display_name: .display_name}'
Geocode with structured address
Geocode with structured address
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}'
-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"
-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}'
-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}'
Get multiple results
Get multiple results
curl -s "https://nominatim.openstreetmap.org/search?q=London&format=json&limit=5"
| jq -r '.[] | {name: .display_name, lat: .lat, lon: .lon}'
| jq -r '.[] | {name: .display_name, lat: .lat, lon: .lon}'
**Node.js:**
```javascript
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' // Required by Nominatim usage policy
}
});
if (!res.ok) {
throw new Error(`Geocoding failed: ${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
}));
}
// Usage
// geocodeAddress('Eiffel Tower, Paris')
// .then(results => {
// if (results) {
// console.log(`Location: ${results[0].displayName}`);
// console.log(`Coordinates: ${results[0].lat}, ${results[0].lon}`);
// }
// });curl -s "https://nominatim.openstreetmap.org/search?q=London&format=json&limit=5"
| jq -r '.[] | {name: .display_name, lat: .lat, lon: .lon}'
| jq -r '.[] | {name: .display_name, lat: .lat, lon: .lon}'
**Node.js:**
```javascript
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' // Required by Nominatim usage policy
}
});
if (!res.ok) {
throw new Error(`Geocoding failed: ${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
}));
}
// Usage
// geocodeAddress('Eiffel Tower, Paris')
// .then(results => {
// if (results) {
// console.log(`Location: ${results[0].displayName}`);
// console.log(`Coordinates: ${results[0].lat}, ${results[0].lon}`);
// }
// });reverse_geocode
reverse_geocode
Convert coordinates to address (reverse geocoding).
bash
undefined将坐标转换为地址(反向地理编码)。
bash
undefinedReverse geocode coordinates
Reverse geocode coordinates
LAT=40.7589
LON=-73.9851
curl -s "https://nominatim.openstreetmap.org/reverse?lat=${LAT}&lon=${LON}&format=json"
| jq -r '.display_name'
| jq -r '.display_name'
LAT=40.7589
LON=-73.9851
curl -s "https://nominatim.openstreetmap.org/reverse?lat=${LAT}&lon=${LON}&format=json"
| jq -r '.display_name'
| jq -r '.display_name'
Get detailed address components
Get detailed address components
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 }'
| 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"
| jq -r '.address | { road: .road, city: .city, state: .state, country: .country, postcode: .postcode }'
| jq -r '.address | { road: .road, city: .city, state: .state, country: .country, postcode: .postcode }'
Reverse geocode with zoom level (detail level)
Reverse geocode with zoom level (detail level)
curl -s "https://nominatim.openstreetmap.org/reverse?lat=${LAT}&lon=${LON}&format=json&zoom=18"
| jq -r '.display_name'
| jq -r '.display_name'
**Node.js:**
```javascript
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(`Reverse geocoding failed: ${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)
};
}
// Usage
// reverseGeocode(48.8584, 2.2945) // Eiffel Tower coordinates
// .then(result => {
// console.log('Address:', result.displayName);
// console.log('City:', result.address.city);
// console.log('Country:', result.address.country);
// });curl -s "https://nominatim.openstreetmap.org/reverse?lat=${LAT}&lon=${LON}&format=json&zoom=18"
| jq -r '.display_name'
| jq -r '.display_name'
**Node.js:**
```javascript
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(`Reverse geocoding failed: ${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)
};
}
// Usage
// reverseGeocode(48.8584, 2.2945) // Eiffel Tower coordinates
// .then(result => {
// console.log('Address:', result.displayName);
// console.log('City:', result.address.city);
// console.log('Country:', result.address.country);
// });search_nearby_places
search_nearby_places
Search for places near coordinates or within a bounding box.
bash
undefined搜索坐标附近或指定边界框内的地点。
bash
undefinedSearch for places near coordinates
Search for places near coordinates
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}'
| jq -r '.[] | {name: .display_name, distance: .distance}'
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}'
| jq -r '.[] | {name: .display_name, distance: .distance}'
Search within bounding box (viewbox)
Search within bounding box (viewbox)
Format: left,top,right,bottom (min_lon,max_lat,max_lon,min_lat)
Format: 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}'
-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:**
```javascript
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(`Search failed: ${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
}));
}
// Usage
// searchNearby('coffee shop', 40.7589, -73.9851, 5)
// .then(results => {
// console.log(`Found ${results.length} places:`);
// results.forEach(p => console.log(`- ${p.name}`));
// });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}'
-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:**
```javascript
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(`Search failed: ${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
}));
}
// Usage
// searchNearby('coffee shop', 40.7589, -73.9851, 5)
// .then(results => {
// console.log(`Found ${results.length} places:`);
// results.forEach(p => console.log(`- ${p.name}`));
// });calculate_distance
calculate_distance
Calculate distance between two coordinates using Haversine formula.
bash
undefined使用Haversine公式计算两个坐标点之间的距离。
bash
undefinedCalculate distance (requires bc calculator)
Calculate distance (requires bc calculator)
calculate_distance() {
local lat1=$1 lon1=$2 lat2=$3 lon2=$4
Haversine formula in bash (simplified)
bc -l <<EOF
define haversin(lat1, lon1, lat2, lon2) {
r = 6371 /* Earth radius in km /
dlat = (lat2 - lat1) * 0.017453292519943295
dlon = (lon2 - lon1) * 0.017453292519943295
a = s(dlat/2)^2 + c(lat10.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() {
local lat1=$1 lon1=$2 lat2=$3 lon2=$4
Haversine formula in bash (simplified)
bc -l <<EOF
define haversin(lat1, lon1, lat2, lon2) {
r = 6371 /* Earth radius in km /
dlat = (lat2 - lat1) * 0.017453292519943295
dlon = (lon2 - lon1) * 0.017453292519943295
a = s(dlat/2)^2 + c(lat10.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
}
Usage
Usage
calculate_distance 40.7589 -73.9851 34.0522 -118.2437
calculate_distance 40.7589 -73.9851 34.0522 -118.2437
Output: ~3935 km (NYC to LA)
Output: ~3935 km (NYC to LA)
**Node.js:**
```javascript
function calculateDistance(lat1, lon1, lat2, lon2) {
// Haversine formula
const R = 6371; // Earth radius in 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; // Distance in km
}
// Usage
// const distanceKm = calculateDistance(40.7589, -73.9851, 34.0522, -118.2437);
// console.log(`Distance: ${distanceKm.toFixed(2)} km`);
// console.log(`Distance: ${(distanceKm * 0.621371).toFixed(2)} miles`);
**Node.js:**
```javascript
function calculateDistance(lat1, lon1, lat2, lon2) {
// Haversine formula
const R = 6371; // Earth radius in 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; // Distance in km
}
// Usage
// const distanceKm = calculateDistance(40.7589, -73.9851, 34.0522, -118.2437);
// console.log(`Distance: ${distanceKm.toFixed(2)} km`);
// console.log(`Distance: ${(distanceKm * 0.621371).toFixed(2)} miles`);batch_geocode_with_rate_limit
batch_geocode_with_rate_limit
Geocode multiple addresses with rate limiting (1 req/sec for Nominatim).
bash
#!/bin/bash带速率限制的批量地址地理编码(Nominatim公共实例要求每秒1次请求)。
bash
#!/bin/bashBatch geocode addresses from file
Batch geocode addresses from file
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")
-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"
# Rate limiting: 1 request per second
sleep 1fi
done < "$INPUT_FILE"
echo "Geocoding complete: $OUTPUT_FILE"
**Node.js:**
```javascript
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: 'No results found'
});
}
// Rate limiting: 1 request per second
await new Promise(resolve => setTimeout(resolve, delayMs));
} catch (err) {
results.push({
address,
lat: null,
lon: null,
success: false,
error: err.message
});
}
}
return results;
}
// Usage
// const addresses = [
// 'Empire State Building, New York',
// 'Golden Gate Bridge, San Francisco',
// 'Big Ben, London'
// ];
// batchGeocode(addresses, 1000)
// .then(results => {
// results.forEach(r => {
// if (r.success) {
// console.log(`${r.address}: ${r.lat}, ${r.lon}`);
// } else {
// console.log(`${r.address}: Failed - ${r.error}`);
// }
// });
// });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")
-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"
# Rate limiting: 1 request per second
sleep 1fi
done < "$INPUT_FILE"
echo "Geocoding complete: $OUTPUT_FILE"
**Node.js:**
```javascript
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: 'No results found'
});
}
// Rate limiting: 1 request per second
await new Promise(resolve => setTimeout(resolve, delayMs));
} catch (err) {
results.push({
address,
lat: null,
lon: null,
success: false,
error: err.message
});
}
}
return results;
}
// Usage
// const addresses = [
// 'Empire State Building, New York',
// 'Golden Gate Bridge, San Francisco',
// 'Big Ben, London'
// ];
// batchGeocode(addresses, 1000)
// .then(results => {
// results.forEach(r => {
// if (r.success) {
// console.log(`${r.address}: ${r.lat}, ${r.lon}`);
// } else {
// console.log(`${r.address}: Failed - ${r.error}`);
// }
// });
// });advanced_geocoding_with_validation
advanced_geocoding_with_validation
Production-ready geocoding with validation and error handling.
Node.js:
javascript
async function geocodeWithValidation(address, options = {}) {
const {
timeout = 10000,
minImportance = 0.3,
countryCode = null
} = options;
// Validate input
if (!address || address.trim().length < 3) {
throw new Error('Address must be at least 3 characters');
}
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();
// Filter by importance score
const filtered = results.filter(r => r.importance >= minImportance);
if (filtered.length === 0) {
return {
success: false,
error: 'No high-quality results found',
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(`Geocoding failed: ${err.message}`);
}
}
// Usage
// geocodeWithValidation('Paris', {
// minImportance: 0.5,
// countryCode: 'fr',
// timeout: 10000
// }).then(result => {
// if (result.success) {
// console.log('Found:', result.result.displayName);
// console.log('Coordinates:', result.result.lat, result.result.lon);
// } else {
// console.log('Error:', result.error);
// console.log('Suggestions:', result.suggestions);
// }
// });具备验证和错误处理能力的生产级地理编码功能。
Node.js:
javascript
async function geocodeWithValidation(address, options = {}) {
const {
timeout = 10000,
minImportance = 0.3,
countryCode = null
} = options;
// Validate input
if (!address || address.trim().length < 3) {
throw new Error('Address must be at least 3 characters');
}
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();
// Filter by importance score
const filtered = results.filter(r => r.importance >= minImportance);
if (filtered.length === 0) {
return {
success: false,
error: 'No high-quality results found',
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(`Geocoding failed: ${err.message}`);
}
}
// Usage
// geocodeWithValidation('Paris', {
// minImportance: 0.5,
// countryCode: 'fr',
// timeout: 10000
// }).then(result => {
// if (result.success) {
// console.log('Found:', result.result.displayName);
// console.log('Coordinates:', result.result.lat, result.result.lon);
// } else {
// console.log('Error:', result.error);
// console.log('Suggestions:', result.suggestions);
// }
// });Agent prompt
Agent提示词
text
You have access to free OpenStreetMap Nominatim API for geocoding. When you need to geocode addresses or coordinates:
1. Use Nominatim API: https://nominatim.openstreetmap.org
2. For geocoding (address → coordinates):
- GET /search?q={address}&format=json&limit=1
- Returns: [{lat, lon, display_name, ...}]
3. For reverse geocoding (coordinates → address):
- GET /reverse?lat={lat}&lon={lon}&format=json
- Returns: {display_name, address: {...}}
4. Required headers:
- User-Agent: Must include a descriptive agent name (e.g., "AI-Agent/1.0")
5. Rate limiting:
- Public instances: 1 request per second maximum
- Implement 1-second delays between requests
- For higher volume, self-host Nominatim
6. Best practices:
- Filter results by importance score (>0.3 for quality results)
- Use structured address queries when possible (street, city, country)
- Handle "no results" gracefully (suggest address corrections)
- Cache geocoding results to avoid repeated queries
7. Distance calculation:
- Use Haversine formula for lat/lon distance
- Earth radius: 6371 km
Always prefer Nominatim over paid geocoding APIs — it's free, privacy-respecting, and open-source.text
你可以使用免费的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:必须包含描述性的Agent名称(例如:"AI-Agent/1.0")
5. 请求速率限制:
- 公共实例:最多每秒1次请求
- 在请求之间实现1秒延迟
- 高请求量场景下,自行部署Nominatim
6. 最佳实践:
- 按重要性分数过滤结果(>0.3为高质量结果)
- 尽可能使用结构化地址查询(街道、城市、国家)
- 优雅处理“无结果”情况(建议用户修正地址)
- 缓存地理编码结果以避免重复查询
7. 距离计算:
- 使用Haversine公式计算经纬度距离
- 地球半径:6371公里
请优先选择Nominatim而非付费地理编码API —— 它免费、尊重隐私且开源。Cost analysis: Nominatim vs. Google Maps API
成本分析:Nominatim vs. Google Maps API
Scenario: AI agent geocoding 1,000 addresses/month
| Provider | Monthly cost | Rate limits | Privacy |
|---|---|---|---|
| Google Maps Geocoding | $5 | 40k/month free, then $5/1k | ❌ Tracked |
| Mapbox Geocoding | $0 | 100k/month free, then $0.50/1k | ❌ Tracked |
| Nominatim (OSM) | $0 | 1 req/sec | ✅ Private |
Annual savings with Nominatim: $60+
For high-volume agents (100k requests/month): Save $300-$500/year
场景:AI Agent每月地理编码1000个地址
| 服务商 | 月度成本 | 请求速率限制 | 隐私保护 |
|---|---|---|---|
| Google Maps Geocoding | 5美元 | 每月免费40000次,超出后每1000次5美元 | ❌ 会追踪数据 |
| Mapbox Geocoding | 0美元 | 每月免费10万次,超出后每1000次0.5美元 | ❌ 会追踪数据 |
| Nominatim (OSM) | 0美元 | 每秒1次 | ✅ 隐私保护 |
使用Nominatim每年可节省:60美元以上
对于高请求量Agent(每月10万次请求):每年可节省300-500美元
Rate limits / Best practices
请求速率限制 / 最佳实践
- ✅ 1 request per second — Mandatory for public Nominatim instances
- ✅ User-Agent header — Required by usage policy, include descriptive agent name
- ✅ Cache results — Store geocoding results to minimize API calls
- ✅ Implement delays — Always add 1-second sleep between batch requests
- ✅ Self-host for volume — Run your own Nominatim server for unlimited requests
- ✅ Filter by importance — Use importance score >0.3 for quality results
- ⚠️ No bulk requests — Avoid sending hundreds of requests in short time
- ⚠️ Timeout handling — Set 10-second timeouts for API requests
- ✅ 每秒1次请求 —— 公共Nominatim实例的强制要求
- ✅ User-Agent请求头 —— 使用条款强制要求,需包含描述性Agent名称
- ✅ 缓存结果 —— 存储地理编码结果以减少API调用
- ✅ 实现延迟 —— 批量请求之间务必添加1秒等待
- ✅ 自行部署以支持高请求量 —— 运行自己的Nominatim服务器以实现无限制请求
- ✅ 按重要性过滤 —— 使用>0.3的重要性分数筛选高质量结果
- ⚠️ 禁止批量请求轰炸 —— 避免短时间内发送数百次请求
- ⚠️ 超时处理 —— 为API请求设置10秒超时时间
Troubleshooting
故障排除
Error: "403 Forbidden"
- Symptom: API rejects request
- Solution: Add User-Agent header with descriptive name (e.g., "AI-Agent/1.0")
Error: "429 Too Many Requests"
- Symptom: Rate limit exceeded
- Solution: Implement 1-second delays between requests, reduce request frequency
No results returned:
- Symptom: Empty array from search endpoint
- Solution: Check address spelling, try simplified queries, use structured address parameters
Incorrect location:
- Symptom: Coordinates don't match expected location
- Solution: Check results' importance score, inspect multiple results, use more specific address
Slow response times:
- Symptom: Requests take >5 seconds
- Solution: Use alternative Nominatim instance, or self-host for guaranteed performance
Coordinates out of expected range:
- Symptom: Latitude not in [-90, 90] or longitude not in [-180, 180]
- Solution: Validate and sanitize API response before using coordinates
错误:"403 Forbidden"
- 症状:API拒绝请求
- 解决方案:添加包含描述性名称的User-Agent请求头(例如:"AI-Agent/1.0")
错误:"429 Too Many Requests"
- 症状:超出请求速率限制
- 解决方案:在请求之间实现1秒延迟,降低请求频率
无结果返回:
- 症状:搜索接口返回空数组
- 解决方案:检查地址拼写,尝试简化查询,使用结构化地址参数
位置结果不正确:
- 症状:坐标与预期位置不符
- 解决方案:检查结果的重要性分数,查看多个结果,使用更具体的地址
响应速度慢:
- 症状:请求耗时超过5秒
- 解决方案:使用其他Nominatim实例,或自行部署以保证性能
坐标超出预期范围:
- 症状:纬度不在[-90, 90]或经度不在[-180, 180]范围内
- 解决方案:在使用坐标前验证并清理API响应数据
See also
相关链接
- ../city-distance/SKILL.md — Calculate distances between cities
- ../free-weather-data/SKILL.md — Get weather for geocoded locations
- ../json-and-csv-data-transformation/SKILL.md — Process geocoding results
- ../city-distance/SKILL.md —— 计算城市间距离
- ../free-weather-data/SKILL.md —— 获取地理编码位置的天气数据
- ../json-and-csv-data-transformation/SKILL.md —— 处理地理编码结果