Geocode Location
Convert address to coordinates (forward) or coordinates to address (reverse) using Nominatim
Source Code
const [query, mode = "forward"] = process.argv.slice(2);
if (!query) {
console.error("Error: query is required");
process.exit(1);
}
const USER_AGENT = "SaunaGeoOperations/1.0";
// Check if string looks like coordinates (lat,lon)
function isCoordinates(str) {
const coordPattern = /^-?\d+\.?\d*,\s*-?\d+\.?\d*$/;
return coordPattern.test(str.trim());
}
// Forward geocode: address -> coordinates
async function forwardGeocode(address) {
const params = new URLSearchParams({
q: address,
format: "json",
limit: "1",
addressdetails: "1",
});
const response = await fetch(
`https://nominatim.openstreetmap.org/search?${params}`,
{ headers: { "User-Agent": USER_AGENT } }
);
if (!response.ok) {
throw new Error(`Geocoding failed: ${response.status}`);
}
const results = await response.json();
if (!results.length) {
throw new Error(`Location not found: "${address}"`);
}
const result = results[0];
return {
lat: parseFloat(result.lat),
lon: parseFloat(result.lon),
displayName: result.display_name,
address: result.address || {},
boundingBox: result.boundingbox
? {
south: parseFloat(result.boundingbox[0]),
north: parseFloat(result.boundingbox[1]),
west: parseFloat(result.boundingbox[2]),
east: parseFloat(result.boundingbox[3]),
}
: null,
};
}
// Reverse geocode: coordinates -> address
async function reverseGeocode(lat, lon) {
const params = new URLSearchParams({
lat: lat.toString(),
lon: lon.toString(),
format: "json",
addressdetails: "1",
});
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?${params}`,
{ headers: { "User-Agent": USER_AGENT } }
);
if (!response.ok) {
throw new Error(`Reverse geocoding failed: ${response.status}`);
}
const result = await response.json();
if (result.error) {
throw new Error(`Reverse geocoding failed: ${result.error}`);
}
return {
lat: parseFloat(result.lat),
lon: parseFloat(result.lon),
displayName: result.display_name,
address: result.address || {},
};
}
async function main() {
if (mode === "reverse") {
// Reverse geocoding: expect coordinates
if (!isCoordinates(query)) {
console.error(
"Error: reverse mode requires coordinates in 'lat,lon' format"
);
process.exit(1);
}
const [latStr, lonStr] = query.split(",").map((s) => s.trim());
const lat = parseFloat(latStr);
const lon = parseFloat(lonStr);
console.log(`Reverse geocoding: ${lat}, ${lon}`);
const result = await reverseGeocode(lat, lon);
console.log(`Found: ${result.displayName}`);
console.log(
JSON.stringify({
success: true,
mode: "reverse",
input: { lat, lon },
result,
})
);
} else {
// Forward geocoding: address -> coordinates
// If already coordinates, still run through Nominatim for validation/enrichment
console.log(`Forward geocoding: "${query}"`);
const result = await forwardGeocode(query);
console.log(`Found: ${result.displayName}`);
console.log(`Coordinates: ${result.lat}, ${result.lon}`);
console.log(
JSON.stringify({
success: true,
mode: "forward",
input: query,
result,
})
);
}
}
main().catch((err) => {
console.error("Geocoding failed:", err.message);
process.exit(1);
});