Basic data ingestion and analytics
This commit is contained in:
162
server.ts
162
server.ts
@@ -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
|
||||
===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user