Capture Static Map
Capture a map tile from an address or coordinates using OpenStreetMap (no API key required)
Source Code
import fs from "fs";
import path from "path";
const [location, zoom = "13", outputDir] = process.argv.slice(2);
if (!location) {
console.error("Error: location is required");
process.exit(1);
}
if (!outputDir) {
console.error("Error: outputDir is required");
process.exit(1);
}
// Check if location is already coordinates (lat,lng)
function isCoordinates(loc) {
const coordPattern = /^-?\d+\.?\d*,\s*-?\d+\.?\d*$/;
return coordPattern.test(loc.trim());
}
// Convert lat/lon to OSM tile coordinates
function latLonToTile(lat, lon, z) {
const x = Math.floor(((lon + 180) / 360) * Math.pow(2, z));
const y = Math.floor(
((1 -
Math.log(
Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180)
) /
Math.PI) /
2) *
Math.pow(2, z)
);
return { x, y };
}
// Geocode address to coordinates using Nominatim (free, no API key)
async function geocode(address) {
const params = new URLSearchParams({
q: address,
format: "json",
limit: "1",
});
const response = await fetch(
`https://nominatim.openstreetmap.org/search?${params}`,
{
headers: {
"User-Agent": "TreasureMapCreator/1.0",
},
}
);
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}"`);
}
return {
lat: parseFloat(results[0].lat),
lon: parseFloat(results[0].lon),
displayName: results[0].display_name,
};
}
async function main() {
console.log(`Capturing map for: "${location}"`);
let lat, lon;
const zoomLevel = parseInt(zoom);
// Parse coordinates or geocode address
if (isCoordinates(location)) {
const [latStr, lonStr] = location.split(",").map((s) => s.trim());
lat = parseFloat(latStr);
lon = parseFloat(lonStr);
console.log(`Using coordinates: ${lat}, ${lon}`);
} else {
console.log("Geocoding address...");
const geo = await geocode(location);
lat = geo.lat;
lon = geo.lon;
console.log(`Found: ${geo.displayName}`);
console.log(`Coordinates: ${lat}, ${lon}`);
}
// Calculate tile coordinates
const tile = latLonToTile(lat, lon, zoomLevel);
console.log(`Zoom: ${zoomLevel}, Tile: x=${tile.x}, y=${tile.y}`);
// Fetch tile from official OSM tile server (free, reliable, no API key)
const tileUrl = `https://tile.openstreetmap.org/${zoomLevel}/${tile.x}/${tile.y}.png`;
console.log("Fetching map tile from OpenStreetMap...");
const response = await fetch(tileUrl, {
headers: {
"User-Agent": "TreasureMapCreator/1.0",
},
});
if (!response.ok) {
const text = await response.text();
console.error(`API error (${response.status}): ${text}`);
process.exit(1);
}
const contentType = response.headers.get("content-type");
if (!contentType?.includes("image")) {
const text = await response.text();
console.error(`Unexpected response type: ${contentType}`);
console.error(text);
process.exit(1);
}
const buffer = Buffer.from(await response.arrayBuffer());
// Generate filename from location
const slug = location
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 40);
fs.mkdirSync(outputDir, { recursive: true });
const outputPath = path.join(outputDir, `map-${slug}.png`);
fs.writeFileSync(outputPath, buffer);
console.log(`✓ Saved: ${outputPath}`);
console.log(
JSON.stringify({
success: true,
path: outputPath,
location: location,
coordinates: { lat, lon },
zoom: zoomLevel,
tile: { x: tile.x, y: tile.y },
size: "256x256",
})
);
}
main().catch((err) => {
console.error("Failed:", err.message);
process.exit(1);
});