77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
export interface ParsedAddress {
|
||
city: string;
|
||
street: string;
|
||
house: string;
|
||
fullAddress: string;
|
||
}
|
||
|
||
const NOMINATIM_HEADERS = {
|
||
'User-Agent': 'MVKID/1.0 (sso@idpmvk.lpr)',
|
||
Accept: 'application/json'
|
||
};
|
||
|
||
export async function reverseGeocode(lat: number, lng: number): Promise<ParsedAddress | null> {
|
||
try {
|
||
const url = `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${encodeURIComponent(String(lat))}&lon=${encodeURIComponent(String(lng))}&accept-language=ru`;
|
||
const response = await fetch(url, { headers: NOMINATIM_HEADERS });
|
||
if (!response.ok) return null;
|
||
const data = (await response.json()) as {
|
||
display_name?: string;
|
||
address?: Record<string, string | undefined>;
|
||
};
|
||
const addr = data.address ?? {};
|
||
const city = addr.city || addr.town || addr.village || addr.municipality || addr.state || '';
|
||
const street = addr.road || addr.pedestrian || addr.footway || addr.neighbourhood || '';
|
||
const house = addr.house_number || '';
|
||
return {
|
||
city,
|
||
street,
|
||
house,
|
||
fullAddress: data.display_name ?? formatParsed(city, street, house)
|
||
};
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export async function forwardGeocode(query: string): Promise<{ lat: number; lng: number; parsed: ParsedAddress } | null> {
|
||
const trimmed = query.trim();
|
||
if (!trimmed) return null;
|
||
try {
|
||
const url = `https://nominatim.openstreetmap.org/search?format=jsonv2&q=${encodeURIComponent(trimmed)}&limit=1&accept-language=ru`;
|
||
const response = await fetch(url, { headers: NOMINATIM_HEADERS });
|
||
if (!response.ok) return null;
|
||
const data = (await response.json()) as Array<{
|
||
lat: string;
|
||
lon: string;
|
||
display_name?: string;
|
||
address?: Record<string, string | undefined>;
|
||
}>;
|
||
const first = data[0];
|
||
if (!first) return null;
|
||
const addr = first.address ?? {};
|
||
const city = addr.city || addr.town || addr.village || addr.municipality || '';
|
||
const street = addr.road || addr.pedestrian || '';
|
||
const house = addr.house_number || '';
|
||
return {
|
||
lat: Number(first.lat),
|
||
lng: Number(first.lon),
|
||
parsed: {
|
||
city,
|
||
street,
|
||
house,
|
||
fullAddress: first.display_name ?? formatParsed(city, street, house)
|
||
}
|
||
};
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function formatParsed(city: string, street: string, house: string) {
|
||
const parts = [];
|
||
if (city) parts.push(`г. ${city}`);
|
||
if (street || house) parts.push([street, house].filter(Boolean).join(', '));
|
||
return parts.join(', ');
|
||
}
|