UNPKG

gtfs-bods

Version:

A CLI tool for processing UK Bus Open Data Service (BODS) GTFS data - import, export, and query transit data with ease

277 lines • 12.7 kB
#!/usr/bin/env node import { Command } from 'commander'; import chalk from 'chalk'; import { importGtfs, exportGtfs } from 'gtfs'; import path from 'path'; import { access, constants } from 'fs/promises'; import { GTFSQueries } from './queries.js'; const program = new Command(); // Helper function to check if file exists async function fileExists(filePath) { try { await access(filePath, constants.F_OK); return true; } catch { return false; } } // Helper function to create config for import function createImportConfig(zipFile, dbFile, agencyKey = 'gtfs-data') { return { agencies: [ { agency_key: agencyKey, path: path.resolve(zipFile) } ], sqlitePath: path.resolve(dbFile), verbose: true, logFunction: false }; } // Helper function to create config for export function createExportConfig(dbFile, outputDir, agencyKey = 'gtfs-data') { return { agencies: [ { agency_key: agencyKey } ], sqlitePath: path.resolve(dbFile), exportPath: path.resolve(outputDir), verbose: true }; } program .name('gtfs-bods') .description('CLI tool for processing UK Bus Open Data Service (BODS) GTFS data') .version('1.0.0'); // Import command program .command('import') .description('Import GTFS zip file into SQLite database') .argument('<zip-file>', 'Path to GTFS zip file') .argument('<db-file>', 'Path to output SQLite database file') .option('-k, --agency-key <key>', 'Agency key identifier', 'gtfs-data') .option('-v, --verbose', 'Enable verbose logging', true) .action(async (zipFile, dbFile, options) => { try { console.log(chalk.blue('šŸš€ Starting GTFS import process...')); console.log(chalk.gray(`šŸ“ Input: ${zipFile}`)); console.log(chalk.gray(`šŸ—„ļø Output: ${dbFile}`)); // Check if input file exists if (!(await fileExists(zipFile))) { console.error(chalk.red('āŒ Error: Input GTFS file not found:'), zipFile); process.exit(1); } // Create import configuration const config = createImportConfig(zipFile, dbFile, options.agencyKey); console.log(chalk.yellow('šŸ“„ Importing GTFS data...')); console.log(chalk.gray('ā³ This may take several minutes for large files...')); const startTime = Date.now(); await importGtfs(config); const endTime = Date.now(); const duration = Math.round((endTime - startTime) / 1000); console.log(chalk.green('āœ… Import completed successfully!')); console.log(chalk.gray(`ā±ļø Import took ${duration} seconds`)); console.log(chalk.gray(`šŸ“Š Database saved to: ${path.resolve(dbFile)}`)); } catch (error) { console.error(chalk.red('āŒ Import failed:'), error); process.exit(1); } }); // Export command program .command('export') .description('Export SQLite database back to GTFS format') .argument('<db-file>', 'Path to SQLite database file') .argument('<output-dir>', 'Path to output directory for GTFS files') .option('-k, --agency-key <key>', 'Agency key identifier', 'gtfs-data') .option('-v, --verbose', 'Enable verbose logging', true) .action(async (dbFile, outputDir, options) => { try { console.log(chalk.blue('šŸš€ Starting GTFS export process...')); console.log(chalk.gray(`šŸ—„ļø Input: ${dbFile}`)); console.log(chalk.gray(`šŸ“ Output: ${outputDir}`)); // Check if database file exists if (!(await fileExists(dbFile))) { console.error(chalk.red('āŒ Error: Database file not found:'), dbFile); process.exit(1); } // Create export configuration const config = createExportConfig(dbFile, outputDir, options.agencyKey); console.log(chalk.yellow('šŸ“¤ Exporting GTFS data...')); const startTime = Date.now(); await exportGtfs(config); const endTime = Date.now(); const duration = Math.round((endTime - startTime) / 1000); console.log(chalk.green('āœ… Export completed successfully!')); console.log(chalk.gray(`ā±ļø Export took ${duration} seconds`)); console.log(chalk.gray(`šŸ“ GTFS files saved to: ${path.resolve(outputDir)}`)); } catch (error) { console.error(chalk.red('āŒ Export failed:'), error); process.exit(1); } }); // Query command program .command('query') .description('Query GTFS database and display information') .argument('<db-file>', 'Path to SQLite database file') .option('-r, --report', 'Generate comprehensive report', false) .option('-a, --agencies', 'List all agencies', false) .option('-R, --routes', 'List all routes', false) .option('-s, --stops', 'List all stops', false) .option('-k, --agency-key <key>', 'Filter by agency key') .option('--route-id <id>', 'Get details for specific route') .option('--area <bounds>', 'Find stops in area (format: minLat,minLon,maxLat,maxLon)') .action(async (dbFile, options) => { try { console.log(chalk.blue('šŸ” Querying GTFS database...')); // Check if database file exists if (!(await fileExists(dbFile))) { console.error(chalk.red('āŒ Error: Database file not found:'), dbFile); process.exit(1); } // Note: The gtfs library automatically uses the database from the last import/export operation // For a CLI tool, we would need to implement a way to switch databases or reimport temporarily if (options.report) { console.log(chalk.yellow('šŸ“Š Generating comprehensive report...')); await GTFSQueries.generateReport(options.agencyKey); } else if (options.agencies) { const agencies = await GTFSQueries.getAllAgencies(); console.log(chalk.green(`šŸ¢ Found ${agencies.length} agencies:`)); agencies.forEach((agency, index) => { console.log(` ${index + 1}. ${chalk.bold(agency.agency_name)} (${agency.agency_id})`); if (agency.agency_url) console.log(` 🌐 ${agency.agency_url}`); if (agency.agency_timezone) console.log(` šŸ• ${agency.agency_timezone}`); }); } else if (options.routes) { const routes = await GTFSQueries.getAllRoutes({ agencyKey: options.agencyKey }); console.log(chalk.green(`🚌 Found ${routes.length} routes:`)); routes.slice(0, 20).forEach((route, index) => { console.log(` ${index + 1}. ${chalk.bold(route.route_short_name || route.route_id)}: ${route.route_long_name || 'No description'}`); }); if (routes.length > 20) { console.log(chalk.gray(` ... and ${routes.length - 20} more routes`)); } } else if (options.stops) { const stops = await GTFSQueries.getAllStops({ agencyKey: options.agencyKey }); console.log(chalk.green(`šŸš Found ${stops.length} stops:`)); stops.slice(0, 20).forEach((stop, index) => { console.log(` ${index + 1}. ${chalk.bold(stop.stop_name)} (${stop.stop_id})`); console.log(` šŸ“ ${stop.stop_lat}, ${stop.stop_lon}`); }); if (stops.length > 20) { console.log(chalk.gray(` ... and ${stops.length - 20} more stops`)); } } else if (options.routeId) { const trips = await GTFSQueries.getTripsForRoute(options.routeId, options.agencyKey); console.log(chalk.green(`🚐 Found ${trips.length} trips for route ${options.routeId}:`)); trips.slice(0, 10).forEach((trip, index) => { console.log(` ${index + 1}. Trip ${trip.trip_id} - ${trip.trip_headsign || 'No headsign'}`); }); } else if (options.area) { const bounds = options.area.split(',').map((n) => parseFloat(n)); if (bounds.length !== 4) { console.error(chalk.red('āŒ Error: Area bounds must be in format minLat,minLon,maxLat,maxLon')); process.exit(1); } const [minLat, minLon, maxLat, maxLon] = bounds; const stops = await GTFSQueries.findStopsInArea(minLat, minLon, maxLat, maxLon, options.agencyKey); console.log(chalk.green(`šŸ“ Found ${stops.length} stops in area:`)); stops.forEach((stop, index) => { console.log(` ${index + 1}. ${stop.stop_name} - ${stop.stop_lat}, ${stop.stop_lon}`); }); } else { // Default: show basic stats const agencies = await GTFSQueries.getAllAgencies(); const routes = await GTFSQueries.getAllRoutes({ agencyKey: options.agencyKey }); const stops = await GTFSQueries.getAllStops({ agencyKey: options.agencyKey }); console.log(chalk.green('šŸ“Š Database Statistics:')); console.log(` šŸ¢ Agencies: ${chalk.bold(agencies.length)}`); console.log(` 🚌 Routes: ${chalk.bold(routes.length)}`); console.log(` šŸš Stops: ${chalk.bold(stops.length)}`); console.log('\n' + chalk.gray('Use --help to see more query options')); } } catch (error) { console.error(chalk.red('āŒ Query failed:'), error); process.exit(1); } }); // Info command program .command('info') .description('Display information about a GTFS file or database') .argument('<file>', 'Path to GTFS zip file or SQLite database') .action(async (file) => { try { if (!(await fileExists(file))) { console.error(chalk.red('āŒ Error: File not found:'), file); process.exit(1); } const ext = path.extname(file).toLowerCase(); if (ext === '.zip') { console.log(chalk.blue('šŸ“¦ GTFS Zip File Information')); console.log(chalk.gray('─'.repeat(40))); console.log(`šŸ“ File: ${chalk.bold(path.basename(file))}`); console.log(`šŸ“ Path: ${path.resolve(file)}`); // You could add zip file analysis here if needed const { stat } = await import('fs/promises'); const stats = await stat(file); console.log(`šŸ“Š Size: ${chalk.bold((stats.size / 1024 / 1024).toFixed(2))} MB`); console.log(`šŸ“… Modified: ${chalk.bold(stats.mtime.toLocaleDateString())}`); } else if (ext === '.db' || ext === '.sqlite') { console.log(chalk.blue('šŸ—„ļø SQLite Database Information')); console.log(chalk.gray('─'.repeat(40))); console.log(`šŸ“ File: ${chalk.bold(path.basename(file))}`); console.log(`šŸ“ Path: ${path.resolve(file)}`); const { stat } = await import('fs/promises'); const stats = await stat(file); console.log(`šŸ“Š Size: ${chalk.bold((stats.size / 1024 / 1024).toFixed(2))} MB`); console.log(`šŸ“… Modified: ${chalk.bold(stats.mtime.toLocaleDateString())}`); // Try to query basic stats try { // Note: For a fully functional CLI, you would need to configure the gtfs library // to use the specific database file here const agencies = await GTFSQueries.getAllAgencies(); const routes = await GTFSQueries.getAllRoutes(); const stops = await GTFSQueries.getAllStops(); console.log('\nšŸ“Š Database Content:'); console.log(` šŸ¢ Agencies: ${chalk.bold(agencies.length)}`); console.log(` 🚌 Routes: ${chalk.bold(routes.length)}`); console.log(` šŸš Stops: ${chalk.bold(stops.length)}`); } catch (error) { console.log(chalk.yellow('\nāš ļø Could not read database content (may not be a valid GTFS database)')); } } else { console.error(chalk.red('āŒ Error: Unsupported file type. Use .zip for GTFS files or .db/.sqlite for databases')); process.exit(1); } } catch (error) { console.error(chalk.red('āŒ Info command failed:'), error); process.exit(1); } }); // Error handling program.configureOutput({ outputError: (str, write) => write(chalk.red(str)) }); program.parse(); //# sourceMappingURL=cli.js.map