/** * 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> { 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 {}; } }