Basic data ingestion and analytics

This commit is contained in:
2026-02-09 01:10:52 +01:00
parent 699c826bf0
commit afd195dab9
12 changed files with 3203 additions and 19 deletions

162
server.ts
View File

@@ -2,9 +2,32 @@ import express, { Request, Response } from 'express';
import GtfsRealtimeBindings from 'gtfs-realtime-bindings';
import { loadGtfsStops, loadGtfsRoutes } from './lib/gtfs';
import { config, StopConfig, RouteConfig } from './config';
import {
initDatabase,
logVehiclePosition,
logVehiclePositions,
logArrival,
getDatabaseStats,
cleanupOldData,
getVehicleHistory,
getRouteVehiclePositions,
getStopArrivalHistory,
getRouteDelayStats,
getStopDelayStats,
getRouteHourlyPattern
} from './lib/database';
const app = express();
// Initialize database
try {
initDatabase();
console.log('Database initialized successfully');
} catch (error) {
console.error('Failed to initialize database:', error);
console.log('Continuing without data logging...');
}
// Load GTFS data
const stops = loadGtfsStops();
const routes = loadGtfsRoutes();
@@ -13,6 +36,11 @@ const routes = loadGtfsRoutes();
import * as path from 'path';
app.use(express.static(path.join(__dirname, 'public')));
// Serve OpenAPI specification
app.get('/openapi.yaml', (req: Request, res: Response) => {
res.sendFile(path.join(__dirname, 'openapi.yaml'));
});
// API Endpoints
app.get('/api/config', (req: Request, res: Response) => {
const stopId = (req.query.stopId as string) || config.defaultStop.stopId;
@@ -105,6 +133,22 @@ app.get('/api/arrivals', async (req: Request, res: Response) => {
isRealtime: stopTime.realtime,
realtimeState: stopTime.realtimeState,
});
// Log arrival to database (use scheduled time as primary timestamp to avoid duplicates)
try {
logArrival({
timestamp: scheduledTime.getTime(),
stopId,
routeId,
scheduledTime: scheduledTime.getTime(),
predictedTime: arrivalTime.getTime(),
delaySeconds: stopTime.arrivalDelay,
isRealtime: stopTime.realtime,
headsign: stopTime.headsign,
});
} catch (dbError) {
// Fail silently if database is not available
}
}
}
}
@@ -232,6 +276,24 @@ app.get('/api/vehicles', async (req: Request, res: Response) => {
console.log(`Found ${routeVehicleIds.size} vehicle IDs for route ${routeId}`);
console.log(`Matched ${activeVehicles.length} vehicles from JSON API`);
// Log vehicle positions to database
try {
const positions = activeVehicles.map(v => ({
timestamp: Date.now(),
vehicleId: v.vehicleId || v.label,
routeId,
tripId: v.tripId,
latitude: v.lat,
longitude: v.lon,
speed: v.speed,
bearing: v.bearing,
currentStatus: v.currentStatus,
}));
logVehiclePositions(positions);
} catch (dbError) {
// Fail silently if database is not available
}
res.json(activeVehicles);
} catch (error) {
console.error('Error fetching vehicles:', error);
@@ -239,6 +301,96 @@ app.get('/api/vehicles', async (req: Request, res: Response) => {
}
});
// Historical Data API Endpoints
app.get('/api/stats/db', async (req: Request, res: Response) => {
try {
const stats = await getDatabaseStats();
res.json(stats);
} catch (error) {
res.status(500).json({ error: 'Database not available' });
}
});
app.get('/api/history/vehicle/:vehicleId', async (req: Request, res: Response) => {
try {
const vehicleId = req.params.vehicleId;
const hours = parseInt(req.query.hours as string) || 24;
const endTime = Date.now();
const startTime = endTime - (hours * 60 * 60 * 1000);
const history = await getVehicleHistory(vehicleId, startTime, endTime);
res.json(history);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch vehicle history' });
}
});
app.get('/api/history/route/:routeId/vehicles', async (req: Request, res: Response) => {
try {
const routeId = req.params.routeId;
const hours = parseInt(req.query.hours as string) || 24;
const endTime = Date.now();
const startTime = endTime - (hours * 60 * 60 * 1000);
const positions = await getRouteVehiclePositions(routeId, startTime, endTime);
res.json(positions);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch route vehicle history' });
}
});
app.get('/api/history/stop/:stopId/arrivals', async (req: Request, res: Response) => {
try {
const stopId = req.params.stopId;
const routeId = (req.query.routeId as string) || '';
const hours = parseInt(req.query.hours as string) || 24;
const endTime = Date.now();
const startTime = endTime - (hours * 60 * 60 * 1000);
const arrivals = await getStopArrivalHistory(stopId, routeId, startTime, endTime);
res.json(arrivals);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch arrival history' });
}
});
app.get('/api/stats/route/:routeId/delays', async (req: Request, res: Response) => {
try {
const routeId = req.params.routeId;
const hours = parseInt(req.query.hours as string) || 24;
const stats = await getRouteDelayStats(routeId, hours);
res.json(stats);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch delay stats' });
}
});
app.get('/api/stats/stop/:stopId/delays', async (req: Request, res: Response) => {
try {
const stopId = req.params.stopId;
const hours = parseInt(req.query.hours as string) || 24;
const stats = await getStopDelayStats(stopId, hours);
res.json(stats);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch delay stats' });
}
});
app.get('/api/stats/route/:routeId/hourly', async (req: Request, res: Response) => {
try {
const routeId = req.params.routeId;
const days = parseInt(req.query.days as string) || 7;
const pattern = await getRouteHourlyPattern(routeId, days);
res.json(pattern);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch hourly pattern' });
}
});
// Start server
app.listen(config.server.port, () => {
console.log(`
@@ -247,12 +399,16 @@ app.listen(config.server.port, () => {
===========================================================================
Server running at: http://localhost:${config.server.port}
Open your browser and navigate to the URL above to view the map.
Pages:
- Main Tracker: http://localhost:${config.server.port}
- Analytics: http://localhost:${config.server.port}/analytics.html
- API Docs: http://localhost:${config.server.port}/api-docs.html
Features:
- Real-time bus arrivals
- Live vehicle locations on map
- Real-time bus arrivals and vehicle tracking
- Interactive map interface
- Historical analytics and delay statistics
- Complete REST API with OpenAPI specification
Press Ctrl+C to stop the server
===========================================================================