/** * Google Maps GeoJSON parser for rMaps place import. * Ported from rmaps-online/src/lib/googleMapsParser.ts (pure TS, no React deps). */ export interface ParsedPlace { name: string; lat: number; lng: number; address?: string; category?: string; } export interface ParseResult { success: boolean; places: ParsedPlace[]; error?: string; totalFeatures: number; } const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB /** * Parse a Google Maps GeoJSON export into a list of places. * Accepts standard GeoJSON FeatureCollection with Point features. */ export function parseGoogleMapsGeoJSON(jsonStr: string): ParseResult { if (jsonStr.length > MAX_FILE_SIZE) { return { success: false, places: [], error: "File too large (max 50 MB)", totalFeatures: 0 }; } let data: any; try { data = JSON.parse(jsonStr); } catch { return { success: false, places: [], error: "Invalid JSON", totalFeatures: 0 }; } if (!data || data.type !== "FeatureCollection" || !Array.isArray(data.features)) { return { success: false, places: [], error: "Not a valid GeoJSON FeatureCollection", totalFeatures: 0 }; } const places: ParsedPlace[] = []; for (const feature of data.features) { if (!feature?.geometry || feature.geometry.type !== "Point") continue; const coords = feature.geometry.coordinates; if (!Array.isArray(coords) || coords.length < 2) continue; const lng = coords[0]; const lat = coords[1]; // Validate coordinates if (typeof lat !== "number" || typeof lng !== "number") continue; if (lat < -90 || lat > 90 || lng < -180 || lng > 180) continue; if (isNaN(lat) || isNaN(lng)) continue; // Extract name from various properties formats const props = feature.properties || {}; const name = props.name || props.Name || props.title || props.Title || props["Google Maps URL"]?.split("/place/")?.pop()?.split("/")?.[0]?.replace(/\+/g, " ") || `Place at ${lat.toFixed(4)}, ${lng.toFixed(4)}`; places.push({ name: String(name).substring(0, 200), lat, lng, address: props.address || props.Address || props.description || undefined, category: props.category || props.Category || undefined, }); } return { success: places.length > 0, places, totalFeatures: data.features.length, ...(places.length === 0 ? { error: "No valid Point features found" } : {}), }; }