# Arquitectura Sistema de Tracking de Trenes en Tiempo Real ## Visión General Sistema web para visualizar en tiempo real la posición de todos los trenes operados por Renfe en España, con capacidad de consultar histórico mediante un timeline slider. ## Fuentes de Datos > 📘 **Nota**: Para información detallada de todas las fuentes disponibles, ver [FUENTES_DATOS.md](FUENTES_DATOS.md) ### Nivel 1: Datos en Tiempo Real (Core) #### GTFS-RT Vehicle Positions (IMPLEMENTADO) - **URL**: `https://gtfsrt.renfe.com/vehicle_positions.pb` - **Formato**: Protocol Buffer (GTFS Realtime) - **Frecuencia**: Cada 30 segundos - **Información**: - Posición GPS (latitud, longitud) - Estado del vehículo (parado, en movimiento) - Velocidad y dirección (bearing) - Identificadores (train_id, trip_id, route_id) - **Servicios**: Principalmente Cercanías - **Uso**: Visualización en tiempo real en mapa #### GTFS-RT Trip Updates - **URL**: `https://gtfsrt.renfe.com/trip_updates_cercanias.pb` - **Formato**: Protocol Buffer (GTFS Realtime) - **Frecuencia**: Cada 30 segundos - **Información**: - Cancelaciones de servicios - Retrasos y adelantos - Cambios de horario - Predicciones de llegada/salida - **Servicios**: Cercanías + AVE + LD + MD - **Uso**: Alertas, predicciones, estadísticas de puntualidad #### GTFS-RT Service Alerts - **URL**: `https://gtfsrt.renfe.com/alerts.pb` - **Formato**: Protocol Buffer (GTFS Realtime) - **Frecuencia**: Cada 30 segundos - **Información**: - Incidencias en la red - Servicios de autobús sustitutorio - Problemas de accesibilidad - Cierres de vías - **Uso**: Notificaciones push, panel de alertas ### Nivel 2: Datos Estáticos (Enriquecimiento) #### GTFS Static de Renfe - **URL**: `https://data.renfe.com/dataset?res_format=GTFS/` - **Formato**: ZIP con archivos CSV - **Frecuencia de actualización**: Semanal/Mensual - **Vigencia actual**: 18 marzo 2025 - 15 diciembre 2025 - **Cobertura**: 366 rutas, 769 paradas - **Archivos principales**: - `routes.txt`: Rutas y líneas - `stops.txt`: Estaciones y paradas - `trips.txt`: Viajes programados - `stop_times.txt`: Horarios - `calendar.txt`: Días de operación - `shapes.txt`: Geometría de rutas (si disponible) - **Uso**: - Enriquecer nombres de rutas - Mostrar horarios programados vs reales - Calcular retrasos - Visualizar rutas en mapa #### Estaciones de Renfe - **URL**: `https://data.renfe.com/dataset` (buscar "estaciones") - **Formato**: JSON, CSV, XML - **Información**: - Coordenadas GPS precisas - Servicios disponibles - Accesibilidad (Atendo) - Código de estación - Instalaciones - **Uso**: - Enriquecer mapa con marcadores de estaciones - Filtrar por estaciones accesibles - Mostrar servicios disponibles ### Nivel 3: Infraestructura Ferroviaria #### ADIF - IDEADIF (Web Map Service) - **URL**: `https://ideadif.adif.es/` - **Protocolo**: WMS (OGC 1.1.1, 1.3.0) - **Normativa**: INSPIRE (Transport Networks) - **Información**: - Trazado de vías - Topología de la red ferroviaria - Estaciones e instalaciones - Túneles y puentes - **Uso**: - Overlay en mapa mostrando vías - Visualización de infraestructura - Contexto geográfico de posiciones #### ADIF - PISERVI - **URL**: `https://www.adif.es/` - **Información**: - Características técnicas de instalaciones - Terminales de mercancías - Instalaciones de mantenimiento - Cambiadores de ancho de vía - **Uso**: Información complementaria de estaciones ### Nivel 4: Datos Regionales y Complementarios #### Consorcios Regionales - **CRTM (Madrid)**: Cercanías Madrid, Metro, Tren Ligero - **CTB (Bilbao)**: Cercanías Bilbao, EuskoTren - **TMB (Barcelona)**: Rodalies Barcelona, FGC - **Formato**: GTFS, GTFS-RT - **Uso**: Integración con redes locales #### Datos Meteorológicos (Futuro) - **AEMET OpenData**: Condiciones climáticas - **Uso**: Correlación retrasos con meteorología #### Datos Estadísticos - **Observatorio del Ferrocarril**: Informes anuales - **Ministerio de Transportes**: Estadísticas mensuales - **Uso**: Analytics, KPIs, tendencias ## Arquitectura Propuesta ### Stack Tecnológico #### Frontend ``` - Framework: React + TypeScript - Mapa: Leaflet.js con tiles de OpenStreetMap - UI Components: Tailwind CSS + shadcn/ui - Gestión de estado: Zustand o Redux Toolkit - Timeline: React Range Slider o Timeline personalizado - Cliente WebSocket: native WebSocket API ``` #### Backend ``` - Runtime: Node.js - Framework: Express.js o Fastify - Base de datos: PostgreSQL + PostGIS (datos geoespaciales) - Cache: Redis (últimas posiciones) - WebSockets: Socket.io - Parser GTFS-RT: gtfs-realtime-bindings (npm) ``` #### Infraestructura ``` - Hosting: Docker containers - Reverse Proxy: Nginx - Proceso de fondo: Node.js worker para polling ``` ### Diagrama de Componentes ``` ┌─────────────────────────────────────────────────────────────┐ │ FRONTEND │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Mapa OSM │ │ Panel Info │ │ Timeline │ │ │ │ (Leaflet) │ │ Tren │ │ Slider │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ └──────────────────┴──────────────────┘ │ │ │ │ │ WebSocket Client │ └───────────────────────────┼──────────────────────────────────┘ │ ┌───────▼──────────┐ │ NGINX (proxy) │ └───────┬──────────┘ │ ┌───────────────────────────┼──────────────────────────────────┐ │ BACKEND │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ WebSocket Server (Socket.io) │ │ │ │ - Broadcast posiciones en tiempo real │ │ │ │ - Gestión de salas por región/tipo tren │ │ │ └───────────────────────┬─────────────────────────────┘ │ │ │ │ │ ┌───────────────────────┼─────────────────────────────┐ │ │ │ API REST (Express/Fastify) │ │ │ │ - GET /trains/current (últimas posiciones) │ │ │ │ - GET /trains/history (histórico por rango) │ │ │ │ - GET /trains/:id (info específica) │ │ │ │ - GET /routes (rutas disponibles) │ │ │ └───────────────────────┬─────────────────────────────┘ │ │ │ │ │ ┌────────────────┼────────────────┐ │ │ │ │ │ │ │ ┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐ │ │ │ Redis │ │PostgreSQL│ │ Worker │ │ │ │ Cache │ │ +PostGIS │ │ Process │ │ │ │ │ │ │ │ │ │ │ │ Últimas │ │ Histórico│ │ Polling │ │ │ │Posiciones│ │ Posiciones│ │ GTFS-RT │ │ │ └──────────┘ │ Rutas │ │ │ │ │ │ Paradas │ └────┬─────┘ │ │ └──────────┘ │ │ └─────────────────────────────────────────┼──────────────────┘ │ ┌─────────────▼─────────────┐ │ Renfe GTFS-RT Feed │ │ vehicle_positions.pb │ │ (polling cada 30 seg) │ └───────────────────────────┘ ``` ## Modelo de Datos ### Base de Datos Principal (PostgreSQL + PostGIS) #### Tabla: train_positions ```sql CREATE TABLE train_positions ( id BIGSERIAL PRIMARY KEY, train_id VARCHAR(50) NOT NULL, trip_id VARCHAR(100), route_id VARCHAR(50), position GEOGRAPHY(POINT, 4326) NOT NULL, -- PostGIS latitude DECIMAL(10, 7) NOT NULL, longitude DECIMAL(10, 7) NOT NULL, bearing DECIMAL(5, 2), -- Dirección 0-360° speed DECIMAL(5, 2), -- km/h status VARCHAR(20), -- STOPPED, MOVING, etc. timestamp TIMESTAMP NOT NULL, recorded_at TIMESTAMP DEFAULT NOW(), INDEX idx_train_timestamp (train_id, timestamp), INDEX idx_position (position) USING GIST, INDEX idx_recorded_at (recorded_at) ); -- Particionamiento por fecha para optimizar consultas históricas CREATE TABLE train_positions_2025_01 PARTITION OF train_positions FOR VALUES FROM ('2025-01-01') TO ('2025-02-01'); ``` #### Tabla: trains ```sql CREATE TABLE trains ( train_id VARCHAR(50) PRIMARY KEY, route_id VARCHAR(50), train_type VARCHAR(50), -- AVE, Cercanías, Regional, etc. service_name VARCHAR(100), first_seen TIMESTAMP, last_seen TIMESTAMP, is_active BOOLEAN DEFAULT true ); ``` #### Tabla: routes ```sql CREATE TABLE routes ( route_id VARCHAR(50) PRIMARY KEY, route_name VARCHAR(200), route_type VARCHAR(50), origin_station VARCHAR(100), destination_station VARCHAR(100), color VARCHAR(7) -- Para visualización en mapa ); ``` #### Tabla: stations ```sql CREATE TABLE stations ( station_id VARCHAR(50) PRIMARY KEY, station_name VARCHAR(200), position GEOGRAPHY(POINT, 4326), latitude DECIMAL(10, 7), longitude DECIMAL(10, 7), station_type VARCHAR(50) ); ``` ### Cache Redis Estructura de claves: ``` trains:current:{train_id} → JSON con última posición trains:active → SET con IDs de trenes activos trains:by_type:{type} → SET con IDs por tipo stats:last_update → Timestamp última actualización ``` ## Flujo de Datos ### 1. Recolección de Datos (Worker Process) ```javascript // Pseudocódigo del worker async function pollGTFSRT() { const response = await fetch('https://gtfsrt.renfe.com/vehicle_positions.pb'); const buffer = await response.arrayBuffer(); const feed = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode( new Uint8Array(buffer) ); for (const entity of feed.entity) { if (entity.vehicle) { const position = { train_id: entity.vehicle.vehicle.id, trip_id: entity.vehicle.trip?.trip_id, route_id: entity.vehicle.trip?.route_id, latitude: entity.vehicle.position.latitude, longitude: entity.vehicle.position.longitude, bearing: entity.vehicle.position.bearing, speed: entity.vehicle.position.speed, status: entity.vehicle.current_status, timestamp: new Date(entity.vehicle.timestamp * 1000) }; // Guardar en Redis (tiempo real) await redis.setex( `trains:current:${position.train_id}`, 300, JSON.stringify(position) ); // Guardar en PostgreSQL (histórico) await db.insertTrainPosition(position); // Broadcast vía WebSocket io.emit('train:update', position); } } } // Ejecutar cada 30 segundos setInterval(pollGTFSRT, 30000); ``` ### 2. API REST Endpoints ```javascript // GET /api/trains/current // Retorna todas las posiciones actuales desde Redis router.get('/trains/current', async (req, res) => { const trainIds = await redis.smembers('trains:active'); const positions = await Promise.all( trainIds.map(id => redis.get(`trains:current:${id}`)) ); res.json(positions.map(JSON.parse)); }); // GET /api/trains/history?from=timestamp&to=timestamp&train_id=xxx // Consulta histórico de PostgreSQL router.get('/trains/history', async (req, res) => { const { from, to, train_id } = req.query; const positions = await db.query(` SELECT * FROM train_positions WHERE timestamp BETWEEN $1 AND $2 ${train_id ? 'AND train_id = $3' : ''} ORDER BY timestamp ASC `, [from, to, train_id].filter(Boolean)); res.json(positions); }); // GET /api/trains/:id // Información detallada de un tren router.get('/trains/:id', async (req, res) => { const train = await db.getTrainInfo(req.params.id); const currentPosition = await redis.get(`trains:current:${req.params.id}`); res.json({ ...train, current: JSON.parse(currentPosition) }); }); ``` ### 3. Frontend - Componentes Principales #### Mapa (MapView.tsx) ```typescript import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'; function MapView({ trains, selectedTrain, onTrainClick }) { return ( {trains.map(train => ( onTrainClick(train) }} > ))} ); } ``` #### Timeline Slider (TimelineSlider.tsx) ```typescript function TimelineSlider({ onTimeChange, minTime, maxTime }) { const [currentTime, setCurrentTime] = useState(Date.now()); const [isPlaying, setIsPlaying] = useState(true); useEffect(() => { if (isPlaying) { const interval = setInterval(() => { setCurrentTime(Date.now()); onTimeChange(Date.now()); }, 1000); return () => clearInterval(interval); } }, [isPlaying]); return (
{ setIsPlaying(false); setCurrentTime(Number(e.target.value)); onTimeChange(Number(e.target.value)); }} /> {new Date(currentTime).toLocaleString()}
); } ``` #### WebSocket Hook (useTrainUpdates.ts) ```typescript function useTrainUpdates() { const [trains, setTrains] = useState([]); useEffect(() => { const socket = io('ws://localhost:3000'); socket.on('train:update', (position) => { setTrains(prev => { const idx = prev.findIndex(t => t.train_id === position.train_id); if (idx >= 0) { const updated = [...prev]; updated[idx] = position; return updated; } else { return [...prev, position]; } }); }); return () => socket.disconnect(); }, []); return trains; } ``` ## Características del Sistema ### Tiempo Real - Actualización automática cada 30 segundos desde el feed GTFS-RT - WebSocket para push de actualizaciones al frontend sin polling - Animación suave de movimiento de trenes entre actualizaciones ### Navegación Temporal (Timeline) - Slider para seleccionar momento específico en el tiempo - Botones play/pause para reproducir histórico - Controles de velocidad de reproducción (1x, 2x, 5x) - Selección de rango de fechas ### Información de Trenes - Popup al hacer clic: ID, ruta, velocidad, dirección, estado - Panel lateral con detalles extendidos - Histórico de posiciones del tren seleccionado (trail en el mapa) - Filtros por tipo de tren (AVE, Cercanías, Regional, etc.) ### Optimizaciones #### Backend - Particionamiento de tablas por fecha - Índices espaciales (PostGIS GIST) - Cache Redis para consultas frecuentes - Compresión de respuestas HTTP (gzip) - Rate limiting en API #### Frontend - Clustering de markers cuando hay muchos trenes cercanos - Lazy loading de datos históricos - Debouncing en timeline slider - Virtual scrolling en listas de trenes - Service Worker para caché offline ## Roadmap de Implementación ### Fase 1: MVP (4-6 semanas) 1. **Semana 1-2**: Backend - Setup PostgreSQL + PostGIS + Redis - Worker para polling GTFS-RT - API REST básica 2. **Semana 3-4**: Frontend - Mapa con OpenStreetMap - Visualización de trenes en tiempo real - WebSocket client 3. **Semana 5-6**: Timeline - Slider temporal - Consultas históricas - Testing y optimización ### Fase 2: Mejoras (4-6 semanas) - Integración de feeds adicionales (data.renfe.com) - Filtros avanzados y búsqueda - Estadísticas y analytics - Exportación de datos ### Fase 3: Avanzado - Predicción de posiciones usando ML - Alertas y notificaciones - API pública - Mobile app ## Costes Estimados ### Desarrollo - **Solo**: 2-3 meses (una persona) - **Equipo pequeño**: 1-1.5 meses (2-3 personas) ### Infraestructura Mensual (estimación) - VPS (4GB RAM, 2 CPU): ~15-25€/mes - PostgreSQL gestionado (opcional): ~25-50€/mes - Redis gestionado (opcional): ~10-20€/mes - Dominio: ~10€/año - **Total**: 25-95€/mes (dependiendo de servicios gestionados) ### Almacenamiento - ~1KB por posición - 1000 trenes × 2 updates/min × 60 min × 24 h = ~2.8M registros/día - ~2.8GB/día → ~84GB/mes - Retención de 3 meses: ~250GB ## Consideraciones Técnicas ### Desafíos 1. **Parsing de Protocol Buffers**: Usar librerías oficiales gtfs-realtime-bindings 2. **Escalabilidad**: Particionamiento de datos históricos, archivado a cold storage 3. **Precisión GPS**: Los datos pueden tener errores, implementar filtrado de outliers 4. **Frecuencia de actualización**: Balancear entre tiempo real y carga del servidor ### Seguridad - Rate limiting en API - CORS configurado correctamente - Sanitización de inputs - HTTPS en producción - Autenticación para endpoints administrativos ## Alternativas y Variantes ### Stack Alternativo Ligero - **Backend**: Python + FastAPI + SQLite/DuckDB - **Frontend**: Vanilla JS + Leaflet (sin framework) - **Deploy**: Single VPS con systemd ### Stack Más Robusto - **Backend**: Go + Gin + TimescaleDB - **Message Queue**: RabbitMQ o Kafka para procesamiento - **Frontend**: Next.js + React Query - **Deploy**: Kubernetes cluster ### Serverless - **Backend**: AWS Lambda + API Gateway - **DB**: DynamoDB + S3 para histórico - **Frontend**: Vercel/Netlify - **Real-time**: AWS IoT Core o Pusher ## Referencias - GTFS Realtime Specification: https://gtfs.org/documentation/realtime/ - Renfe Data Portal: https://data.renfe.com/ - PostGIS Documentation: https://postgis.net/ - Leaflet.js: https://leafletjs.com/ - Socket.io: https://socket.io/ --- ## Integración de Fuentes de Datos ### Estrategia de Sincronización #### Workers de Recolección ```javascript // Arquitectura de workers propuesta const workers = { // Worker 1: GTFS-RT en tiempo real (alta frecuencia) realtimeWorker: { interval: 30000, // 30 segundos sources: [ { name: 'vehicle_positions', url: 'https://gtfsrt.renfe.com/vehicle_positions.pb', parser: parseVehiclePositions, destination: ['postgresql.train_positions', 'redis.current_positions'] }, { name: 'trip_updates', url: 'https://gtfsrt.renfe.com/trip_updates_cercanias.pb', parser: parseTripUpdates, destination: ['postgresql.trip_updates', 'redis.delays'] }, { name: 'service_alerts', url: 'https://gtfsrt.renfe.com/alerts.pb', parser: parseServiceAlerts, destination: ['postgresql.alerts', 'redis.active_alerts'] } ] }, // Worker 2: GTFS Static (baja frecuencia) staticDataWorker: { interval: 86400000, // 24 horas schedule: '0 3 * * *', // 3:00 AM diaria sources: [ { name: 'gtfs_static', url: 'https://data.renfe.com/api/gtfs/latest.zip', parser: parseGTFSZip, files: ['routes', 'stops', 'trips', 'stop_times', 'calendar', 'shapes'], destination: 'postgresql', onUpdate: (changes) => { // Detectar cambios en rutas o estaciones notifyAdmins(changes); invalidateCache(['routes', 'stations']); } } ] }, // Worker 3: Estaciones y metadatos (muy baja frecuencia) metadataWorker: { interval: 604800000, // 7 días schedule: '0 2 * * 0', // Domingo 2:00 AM sources: [ { name: 'renfe_stations', url: 'https://data.renfe.com/api/stations.json', parser: parseStations, destination: 'postgresql.stations', merge: true // Merge con datos existentes }, { name: 'adif_infrastructure', type: 'wms', url: 'https://ideadif.adif.es/wms', parser: extractStationMetadata, destination: 'postgresql.stations', merge: true } ] } }; ``` #### Pipeline de Procesamiento de Datos ``` ┌─────────────────────────────────────────────────────────────┐ │ INGESTA DE DATOS │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ GTFS-RT │ │ GTFS Static │ │ Metadatos │ │ │ │ (30 seg) │ │ (24 horas) │ │ (7 días) │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ └─────────┼──────────────────┼──────────────────┼────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────┐ │ VALIDACIÓN Y TRANSFORMACIÓN │ ├─────────────────────────────────────────────────────────────┤ │ │ │ • Decodificar Protocol Buffers │ │ • Validar campos requeridos │ │ • Normalizar formatos (fechas, coordenadas) │ │ • Detectar outliers (posiciones GPS inválidas) │ │ • Enriquecer con datos estáticos (nombres, rutas) │ │ │ └─────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ ALMACENAMIENTO │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ PostgreSQL │ │ Redis │ │ │ │ + PostGIS │ │ │ │ │ ├──────────────────┤ ├──────────────────┤ │ │ │ • train_positions│◄─────────────┤ • current_pos │ │ │ │ • trip_updates │ │ • active_alerts │ │ │ │ • alerts │ │ • delay_cache │ │ │ │ • routes │ │ • stats_cache │ │ │ │ • stations │ └──────────────────┘ │ │ │ • trips │ │ │ └──────────────────┘ │ │ │ └─────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ DISTRIBUCIÓN │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ WebSocket │ │ API REST │ │ Webhooks │ │ │ │ (push) │ │ (pull) │ │ (eventos) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### Sincronización GTFS Static ```javascript // Módulo de sincronización GTFS Static class GTFSStaticSyncer { async syncGTFSStatic() { const zipUrl = await this.getLatestGTFSUrl(); const localPath = await this.downloadGTFS(zipUrl); const extracted = await this.extractZip(localPath); // Calcular checksum para detectar cambios const newChecksum = this.calculateChecksum(extracted); const oldChecksum = await this.getStoredChecksum(); if (newChecksum === oldChecksum) { console.log('GTFS sin cambios'); return; } // Procesar archivos GTFS const data = { routes: await this.parseGTFSFile(extracted, 'routes.txt'), stops: await this.parseGTFSFile(extracted, 'stops.txt'), trips: await this.parseGTFSFile(extracted, 'trips.txt'), stopTimes: await this.parseGTFSFile(extracted, 'stop_times.txt'), calendar: await this.parseGTFSFile(extracted, 'calendar.txt'), shapes: await this.parseGTFSFile(extracted, 'shapes.txt') }; // Detectar cambios const changes = await this.detectChanges(data); // Actualizar base de datos en transacción await this.db.transaction(async (trx) => { // Insertar/actualizar rutas await this.upsertRoutes(trx, data.routes); // Insertar/actualizar estaciones await this.upsertStops(trx, data.stops); // Insertar/actualizar viajes await this.upsertTrips(trx, data.trips); // Insertar/actualizar horarios await this.upsertStopTimes(trx, data.stopTimes); // Insertar/actualizar formas de rutas if (data.shapes) { await this.upsertShapes(trx, data.shapes); } // Guardar checksum await this.storeChecksum(trx, newChecksum); }); // Invalidar cachés await this.invalidateCaches(['routes', 'stations', 'trips']); // Notificar cambios await this.notifyChanges(changes); console.log('GTFS actualizado:', changes); } async detectChanges(newData) { const changes = { newRoutes: [], updatedRoutes: [], deletedRoutes: [], newStations: [], updatedStations: [], deletedStations: [] }; // Comparar con datos existentes const oldRoutes = await this.db.select('*').from('routes'); // Detectar rutas nuevas for (const route of newData.routes) { const existing = oldRoutes.find(r => r.route_id === route.route_id); if (!existing) { changes.newRoutes.push(route); } else if (JSON.stringify(existing) !== JSON.stringify(route)) { changes.updatedRoutes.push(route); } } // Detectar rutas eliminadas for (const oldRoute of oldRoutes) { const existing = newData.routes.find(r => r.route_id === oldRoute.route_id); if (!existing) { changes.deletedRoutes.push(oldRoute); } } // Similar para estaciones... return changes; } } ``` ### Enriquecimiento de Datos en Tiempo Real ```javascript // Enriquecer posiciones con datos estáticos class PositionEnricher { async enrichPosition(position) { // Datos básicos del feed GTFS-RT const enriched = { ...position, // Añadir información de la ruta route: await this.getRoute(position.route_id), // Añadir información del viaje trip: await this.getTrip(position.trip_id), // Calcular estación más cercana nearestStation: await this.findNearestStation( position.latitude, position.longitude ), // Calcular retraso si hay trip_update delay: await this.calculateDelay(position.trip_id), // Añadir alertas activas alerts: await this.getActiveAlerts(position.route_id), // Información de ocupación (si disponible) occupancy: this.mapOccupancyStatus(position.occupancy_status) }; return enriched; } async findNearestStation(lat, lon) { // Usar PostGIS para encontrar estación más cercana const result = await this.db.raw(` SELECT station_id, station_name, latitude, longitude, ST_Distance( position, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography ) as distance FROM stations ORDER BY distance LIMIT 1 `, [lon, lat]); return result.rows[0]; } async calculateDelay(tripId) { // Buscar trip_update más reciente const update = await this.redis.get(`trip_update:${tripId}`); if (!update) return null; const parsed = JSON.parse(update); // Calcular diferencia entre horario programado y predicción const scheduledArrival = await this.getScheduledArrival(tripId); const predictedArrival = parsed.stop_time_update?.arrival?.time; if (scheduledArrival && predictedArrival) { return (predictedArrival - scheduledArrival) / 60; // Minutos } return null; } } ``` ## Features Avanzadas por Fuente de Datos ### Features con GTFS-RT Vehicle Positions #### 1. Mapa en Tiempo Real - **Descripción**: Visualización de todos los trenes en movimiento - **Implementación**: - Marcadores animados en Leaflet - Colores por tipo de servicio (AVE, Cercanías, etc.) - Rotación del icono según bearing - Clustering para alta densidad - **Datos utilizados**: latitude, longitude, bearing, train_type #### 2. Trail de Movimiento - **Descripción**: Estela que muestra trayectoria reciente del tren - **Implementación**: - Polyline con últimas 10 posiciones - Desvanecimiento gradual (opacity) - Código de colores por velocidad - **Datos utilizados**: Histórico de train_positions (últimos 5 min) #### 3. Heatmap de Tráfico - **Descripción**: Mapa de calor mostrando zonas con más tráfico ferroviario - **Implementación**: - Leaflet.heat plugin - Agregación por celdas geográficas - Filtros por hora del día / día de la semana - **Datos utilizados**: Agregación de train_positions #### 4. Timeline Playback - **Descripción**: Reproducir histórico de posiciones - **Implementación**: - Slider temporal - Controles play/pause/velocidad - Sincronización de múltiples trenes - **Datos utilizados**: train_positions con timestamp #### 5. Seguimiento de Tren Específico - **Descripción**: Centrar cámara en un tren y seguirlo - **Implementación**: - Auto-pan del mapa - Panel lateral con información detallada - Notificaciones de cambios de estado - **Datos utilizados**: train_positions + enriched data ### Features con GTFS-RT Trip Updates #### 6. Monitor de Puntualidad - **Descripción**: Dashboard de retrasos y cancelaciones - **Implementación**: - Gráficos de puntualidad por línea - Ranking de rutas más puntuales - Tendencias temporales - **Datos utilizados**: trip_updates, delay calculations #### 7. Predicciones de Llegada - **Descripción**: Mostrar hora estimada de llegada a próximas estaciones - **Implementación**: - Lista de próximas paradas - Comparación horario programado vs predicho - Indicador visual de retraso/adelanto - **Datos utilizados**: stop_time_updates #### 8. Alertas de Cancelación - **Descripción**: Notificaciones push de trenes cancelados - **Implementación**: - WebSocket event al detectar cancelación - Notificación en navegador - Email/SMS para suscripciones - **Datos utilizados**: trip_updates con schedule_relationship=CANCELED #### 9. Análisis de Retrasos - **Descripción**: Identificar patrones de retrasos - **Implementación**: - Análisis por hora del día - Análisis por estación - Correlación con meteorología - Predicción ML de retrasos - **Datos utilizados**: Histórico de delays, weather data ### Features con GTFS-RT Service Alerts #### 10. Panel de Incidencias - **Descripción**: Listado de alertas activas - **Implementación**: - Categorización por severidad - Filtros por línea/estación - Mapa con marcadores de incidencias - **Datos utilizados**: alerts table #### 11. Notificaciones Personalizadas - **Descripción**: Alertas para rutas favoritas - **Implementación**: - Sistema de suscripciones - Push notifications - Filtros por tipo de alerta - **Datos utilizados**: alerts + user preferences #### 12. Histórico de Incidencias - **Descripción**: Análisis de incidencias pasadas - **Implementación**: - Timeline de alertas - Estadísticas por tipo - Zonas problemáticas - **Datos utilizados**: alerts historical data ### Features con GTFS Static #### 13. Explorador de Rutas - **Descripción**: Visualización de rutas completas - **Implementación**: - Trazar ruta en mapa usando shapes.txt - Mostrar todas las paradas - Horarios completos del servicio - **Datos utilizados**: routes, trips, shapes, stop_times #### 14. Planificador de Viajes - **Descripción**: Calcular mejor ruta entre dos estaciones - **Implementación**: - Algoritmo A* o Dijkstra - Considerar transbordos - Mostrar horarios disponibles - **Datos utilizados**: routes, trips, stop_times, calendar #### 15. Información de Estaciones - **Descripción**: Detalles completos de estaciones - **Implementación**: - Servicios disponibles - Trenes que paran - Próximas salidas/llegadas - Accesibilidad - **Datos utilizados**: stops, stop_times, station metadata #### 16. Comparador Horario vs Real - **Descripción**: Overlay de horario programado sobre posiciones reales - **Implementación**: - Mostrar ruta teórica vs real - Calcular desviaciones - Métricas de adherencia al horario - **Datos utilizados**: stop_times + train_positions ### Features con Infraestructura ADIF #### 17. Visualización de Vías - **Descripción**: Overlay de red ferroviaria - **Implementación**: - WMS layer de IDEADIF - Estilos por tipo de vía (AVE, convencional) - Toggle on/off - **Datos utilizados**: ADIF WMS service #### 18. Información de Infraestructura - **Descripción**: Detalles técnicos de vías y estaciones - **Implementación**: - Click en vía para ver características - Tipo de electrificación - Ancho de vía - Velocidad máxima - **Datos utilizados**: ADIF PISERVI data ### Features con Datos Históricos #### 19. Análisis de Tráfico - **Descripción**: Patrones de movimiento a lo largo del tiempo - **Implementación**: - Gráficos de volumen por hora/día - Identificar horas punta - Rutas más transitadas - **Datos utilizados**: Agregaciones de train_positions #### 20. Estadísticas de Rendimiento - **Descripción**: KPIs del sistema ferroviario - **Implementación**: - Puntualidad promedio - Distancia recorrida - Velocidad promedio por ruta - Número de servicios diarios - **Datos utilizados**: calculate_train_statistics function #### 21. Exportación de Datos - **Descripción**: Descarga de datasets para análisis externo - **Implementación**: - Export a CSV/JSON/GeoJSON - API para acceso programático - Filtros temporales y geográficos - **Datos utilizados**: Cualquier tabla ### Features Avanzadas Multi-Fuente #### 22. Dashboard de Control - **Descripción**: Vista general del sistema - **Implementación**: - Número de trenes activos - Alertas activas - Puntualidad global - Gráficos en tiempo real - **Datos utilizados**: Todas las fuentes #### 23. Sistema de Recomendaciones - **Descripción**: Sugerir mejor momento para viajar - **Implementación**: - Análisis de patrones históricos de retrasos - Ocupación típica por horario - Condiciones meteorológicas - **Datos utilizados**: Historical delays + weather + occupancy #### 24. Detección de Anomalías - **Descripción**: Identificar comportamientos inusuales - **Implementación**: - ML para detectar patrones anormales - Alertas automáticas a operadores - Análisis de velocidad inusual - Detección de paradas no programadas - **Datos utilizados**: train_positions + trip_updates + ML model #### 25. API Pública - **Descripción**: Acceso programático a datos - **Implementación**: - REST API documentada - GraphQL endpoint - Webhooks para eventos - Rate limiting y autenticación - **Datos utilizados**: Todas las fuentes #### 26. Integración con Asistentes - **Descripción**: Comandos de voz y chatbots - **Implementación**: - "¿Cuándo llega el próximo tren a Madrid?" - "¿Hay retrasos en la línea C-1?" - Integración con Alexa/Google Assistant - **Datos utilizados**: NLP + trip_updates + train_positions #### 27. Gamificación - **Descripción**: Elementos lúdicos para engagement - **Implementación**: - Badges por trenes observados - Ranking de usuarios - Challenges ("visita 10 estaciones diferentes") - Compartir en redes sociales - **Datos utilizados**: User data + train_positions ## Roadmap de Features ### Fase 1: MVP (4-6 semanas) - [x] Arquitectura Docker - [ ] Worker GTFS-RT Vehicle Positions - [ ] Mapa en tiempo real básico - [ ] Panel de información de tren - [ ] Timeline básico - [ ] API REST core ### Fase 2: Enriquecimiento (4-6 semanas) - [ ] Integración GTFS Static - [ ] Sincronización semanal automática - [ ] Trip Updates y Alerts - [ ] Monitor de puntualidad - [ ] Predicciones de llegada - [ ] Panel de incidencias ### Fase 3: Features Avanzadas (8-12 semanas) - [ ] Heatmap de tráfico - [ ] Análisis de retrasos - [ ] Explorador de rutas - [ ] Planificador de viajes - [ ] Overlay infraestructura ADIF - [ ] Dashboard de control ### Fase 4: Analytics y ML (8-12 semanas) - [ ] Análisis histórico de tráfico - [ ] Estadísticas de rendimiento - [ ] Predicción ML de retrasos - [ ] Detección de anomalías - [ ] Sistema de recomendaciones - [ ] Correlación con meteorología ### Fase 5: Ecosistema (Continuo) - [ ] API pública documentada - [ ] Webhooks - [ ] Integraciones con asistentes - [ ] Mobile app (iOS/Android) - [ ] Notificaciones push - [ ] Sistema de suscripciones ## Próximos Pasos 1. ✅ Arquitectura Docker Compose completa 2. ✅ Base de datos con PostGIS y particiones 3. ✅ Sistema de migraciones Flyway 4. ✅ Identificación de fuentes de datos 5. ⬜ Implementar worker GTFS-RT Vehicle Positions 6. ⬜ Desarrollar API REST básica 7. ⬜ Crear frontend con mapa Leaflet 8. ⬜ Implementar WebSocket para tiempo real 9. ⬜ Sincronización GTFS Static 10. ⬜ Trip Updates y Service Alerts