import { latLngBounds, LatLngBounds } from "leaflet"; import { getOverpassPresets, OverpassPreset } from "./overpass-presets"; const OVERPASS_API = "https://overpass.kumi.systems/api/interpreter"; export interface OverpassElement { id: number; tags: Record; type: "node" | "way" | "relation"; lat: number; lon: number; }; export function quoteOverpassString(str: string): string { return `"${str.replace(/[\\"]/g, "\\$&").replace(/\n/g, "\\n").replace(/\t/g, "\\t")}"`; } export function getOverpassBbox(bbox: LatLngBounds): string { return [bbox.getSouth(), bbox.getWest(), bbox.getNorth(), bbox.getEast()].join(","); } export async function getOverpassElements(query: string | OverpassPreset[], bbox: LatLngBounds, timeout: number, limit: number, signal?: AbortSignal): Promise { const normalizedQuery = typeof query == "string" ? `${query}${query[query.length - 1] == ";" ? "" : ";"}` : `(${query.map((q) => `${q.query};`).join("")});`; const data = `[out:json][timeout:${timeout}][bbox:${getOverpassBbox(bbox)}];${normalizedQuery}out center ${limit};`; const response = await fetch(`${OVERPASS_API}?data=${encodeURIComponent(data)}`, { signal }); if (response.headers.get("Content-type")?.includes("text/html")) { const html = (new DOMParser()).parseFromString(await response.text(), "text/html"); const errors = [...html.querySelectorAll("p")].flatMap((p) => (p.innerText.includes("Error") ? [p.innerText] : [])); throw new Error(errors.join("\n")); } const result = await response.json(); if (result.elements.length == 0 && result.remark) throw new Error(result.remark); return result.elements.map((element: any) => ({ ...element, tags: element.tags || {}, ...(element.center ? element.center : {}) })); } export async function validateOverpassQuery(query: string, signal?: AbortSignal): Promise { try { await getOverpassElements(query, latLngBounds([0, 0], [0, 0]), 1, 1, signal); return undefined; } catch (e: any) { return e.message; } } export function isOverpassQueryEmpty(query: string | OverpassPreset[] | undefined): boolean { return !query || (Array.isArray(query) && query.length == 0); } export function isEncodedOverpassQuery(shortQuery: string): boolean { return !!shortQuery && (shortQuery.substr(0, 2).toLowerCase() == "o_"); } export function encodeOverpassQuery(query: string | OverpassPreset[] | undefined): string | undefined { if (isOverpassQueryEmpty(query)) return undefined; else if (typeof query == "string") return `O_${btoa(query).replace(/\+/g, '.').replace(/\//g, '_').replace(/=+$/g, '')}`; else return `o_${query!.map((q) => q.key).join("_")}`; } export function decodeOverpassQuery(shortQuery: string | undefined): string | OverpassPreset[] | undefined { if (!shortQuery) return undefined; else if (shortQuery.startsWith("o_")) return getOverpassPresets(shortQuery.substr(2).split("_")); else if (shortQuery.startsWith("O_")) return atob(shortQuery.substr(2)).replace(/\./g, '+').replace(/_/g, '/'); }