// Regenerate src/server/data/airports.json from the public-domain OurAirports // dataset. Usage: node scripts/generate-airports.mjs // // Keeps airports that have an IATA code AND scheduled service, projecting each // to { code, name, city, country, lat, lng }. import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const SOURCE_URL = 'https://davidmegginson.github.io/ourairports-data/airports.csv'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const OUT_PATH = path.join(__dirname, '..', 'src', 'server', 'data', 'airports.json'); // Minimal RFC-4180 CSV parser (handles quotes, escaped quotes, embedded commas/newlines). function parseCsv(str) { const rows = []; let row = []; let field = ''; let inQuotes = false; for (let i = 0; i < str.length; i++) { const c = str[i]; if (inQuotes) { if (c === '"') { if (str[i + 1] === '"') { field += '"'; i++; } else inQuotes = false; } else field += c; } else if (c === '"') inQuotes = true; else if (c === ',') { row.push(field); field = ''; } else if (c === '\n') { row.push(field); rows.push(row); row = []; field = ''; } else if (c === '\r') { /* ignore */ } else field += c; } if (field.length || row.length) { row.push(field); rows.push(row); } return rows; } async function main() { console.log(`Downloading ${SOURCE_URL} ...`); const res = await fetch(SOURCE_URL, { headers: { 'User-Agent': 'trip-plan-app/0.1 (self-hosted)' } }); if (!res.ok) throw new Error(`download failed: HTTP ${res.status}`); const text = await res.text(); const rows = parseCsv(text); const header = rows[0]; const col = (name) => header.indexOf(name); const iIata = col('iata_code'); const iSched = col('scheduled_service'); const iName = col('name'); const iCity = col('municipality'); const iCountry = col('iso_country'); const iLat = col('latitude_deg'); const iLng = col('longitude_deg'); const airports = []; for (let r = 1; r < rows.length; r++) { const row = rows[r]; if (!row || row.length < header.length) continue; const code = (row[iIata] || '').trim(); if (!code) continue; if ((row[iSched] || '').trim() !== 'yes') continue; const lat = Number(row[iLat]); const lng = Number(row[iLng]); airports.push({ code, name: (row[iName] || '').trim(), city: (row[iCity] || '').trim(), country: (row[iCountry] || '').trim(), lat: Number.isFinite(lat) ? lat : null, lng: Number.isFinite(lng) ? lng : null, }); } fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true }); fs.writeFileSync(OUT_PATH, JSON.stringify(airports)); const bytes = fs.statSync(OUT_PATH).size; console.log(`Wrote ${airports.length} airports to ${OUT_PATH} (${bytes} bytes)`); } main().catch((err) => { console.error(err.message); process.exit(1); });