Files
Tomas Dvorak 48c3e15a38 cleanup
2026-05-05 09:48:07 +02:00

224 lines
5.8 KiB
TypeScript

/**
* Utility functions to parse map URLs from various sources
* Supports: mapy.cz, Google Maps
*/
export interface MapCoordinates {
latitude: number;
longitude: number;
zoom?: number;
address?: string;
source: 'mapy.cz' | 'google-maps' | 'unknown';
// Detailed address components from reverse geocoding
street?: string;
houseNumber?: string;
city?: string;
zip?: string;
country?: string;
}
/**
* Parse mapy.cz URL
* Example: mapy.cz/en/letecka?q=krnov%20stadion&source=firm&id=12954454&ds=2&x=17.6996859&y=50.0947150&z=19
*/
export function parseMapyCzUrl(url: string): MapCoordinates | null {
try {
const urlObj = new URL(url);
// Check if it's a mapy.cz domain
if (!urlObj.hostname.includes('mapy.cz') && !urlObj.hostname.includes('mapy.com')) {
return null;
}
const params = urlObj.searchParams;
const xParam = params.get('x');
const yParam = params.get('y');
const zParam = params.get('z');
const qParam = params.get('q');
if (xParam && yParam) {
const longitude = parseFloat(xParam);
const latitude = parseFloat(yParam);
const zoom = zParam ? parseInt(zParam) : undefined;
if (isNaN(latitude) || isNaN(longitude)) {
return null;
}
return {
latitude,
longitude,
zoom,
address: qParam ? decodeURIComponent(qParam) : undefined,
source: 'mapy.cz',
};
}
return null;
} catch (error) {
console.error('Error parsing mapy.cz URL:', error);
return null;
}
}
/**
* Parse Google Maps URL
* Supports various formats:
* - /maps/place/.../@lat,lng,zoom
* - /maps?q=lat,lng
* - /maps/@lat,lng,zoom
*/
export function parseGoogleMapsUrl(url: string): MapCoordinates | null {
try {
const urlObj = new URL(url);
// Check if it's a Google Maps domain
if (!urlObj.hostname.includes('google.com') && !urlObj.hostname.includes('google.cz')) {
return null;
}
// Try to extract from pathname (/@lat,lng,zoom format)
const pathMatch = urlObj.pathname.match(/@(-?\d+\.\d+),(-?\d+\.\d+),(\d+)(?:[mz])/);
if (pathMatch) {
const latitude = parseFloat(pathMatch[1]);
const longitude = parseFloat(pathMatch[2]);
const zoom = parseInt(pathMatch[3]);
if (!isNaN(latitude) && !isNaN(longitude)) {
// Try to extract place name from pathname
const placeMatch = urlObj.pathname.match(/\/place\/([^/]+)/);
const address = placeMatch ? decodeURIComponent(placeMatch[1].replace(/\+/g, ' ')) : undefined;
return {
latitude,
longitude,
zoom: !isNaN(zoom) ? zoom : undefined,
address,
source: 'google-maps',
};
}
}
// Try to extract from query parameters
const params = urlObj.searchParams;
const qParam = params.get('q');
if (qParam) {
// Check if q parameter contains coordinates (lat,lng format)
const coordMatch = qParam.match(/(-?\d+\.\d+),(-?\d+\.\d+)/);
if (coordMatch) {
const latitude = parseFloat(coordMatch[1]);
const longitude = parseFloat(coordMatch[2]);
if (!isNaN(latitude) && !isNaN(longitude)) {
return {
latitude,
longitude,
address: qParam.includes(',') ? undefined : qParam,
source: 'google-maps',
};
}
}
}
// Try data parameter (some Google Maps links use this)
const dataMatch = urlObj.pathname.match(/!3d(-?\d+\.\d+)!4d(-?\d+\.\d+)/);
if (dataMatch) {
const latitude = parseFloat(dataMatch[1]);
const longitude = parseFloat(dataMatch[2]);
if (!isNaN(latitude) && !isNaN(longitude)) {
return {
latitude,
longitude,
source: 'google-maps',
};
}
}
return null;
} catch (error) {
console.error('Error parsing Google Maps URL:', error);
return null;
}
}
/**
* Main function to parse any supported map URL
*/
export function parseMapUrl(url: string): MapCoordinates | null {
if (!url || typeof url !== 'string') {
return null;
}
// Normalize the URL (add protocol if missing)
let normalizedUrl = url.trim();
if (!normalizedUrl.startsWith('http://') && !normalizedUrl.startsWith('https://')) {
normalizedUrl = 'https://' + normalizedUrl;
}
// Try parsing as mapy.cz
const mapyCzResult = parseMapyCzUrl(normalizedUrl);
if (mapyCzResult) {
return mapyCzResult;
}
// Try parsing as Google Maps
const googleMapsResult = parseGoogleMapsUrl(normalizedUrl);
if (googleMapsResult) {
return googleMapsResult;
}
return null;
}
/**
* Validate if coordinates are within valid ranges
*/
export function validateCoordinates(lat: number, lng: number): boolean {
return (
!isNaN(lat) &&
!isNaN(lng) &&
lat >= -90 &&
lat <= 90 &&
lng >= -180 &&
lng <= 180
);
}
/**
* Reverse geocode coordinates to get detailed address information
* Uses Nominatim API (OpenStreetMap)
*/
export async function reverseGeocode(lat: number, lng: number): Promise<Partial<MapCoordinates>> {
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&addressdetails=1&accept-language=cs`,
{
headers: {
'User-Agent': 'FotbalClub/1.0',
},
}
);
if (!response.ok) {
throw new Error('Reverse geocoding failed');
}
const data = await response.json();
const addr = data.address || {};
return {
address: data.display_name,
street: addr.road || addr.street || addr.pedestrian || addr.footway,
houseNumber: addr.house_number,
city: addr.city || addr.town || addr.village || addr.municipality,
zip: addr.postcode,
country: addr.country || 'Česká republika',
};
} catch (error) {
console.error('Reverse geocoding error:', error);
return {};
}
}