Files
trenes/arquitectura-sistema-tracking-trenes.md

1205 lines
43 KiB
Markdown
Raw Normal View History

# 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 (
<MapContainer center={[40.4168, -3.7038]} zoom={6}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors'
/>
{trains.map(train => (
<Marker
key={train.train_id}
position={[train.latitude, train.longitude]}
icon={getTrainIcon(train.train_type)}
eventHandlers={{
click: () => onTrainClick(train)
}}
>
<Popup>
<TrainInfo train={train} />
</Popup>
</Marker>
))}
</MapContainer>
);
}
```
#### 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 (
<div className="timeline-container">
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<input
type="range"
min={minTime}
max={maxTime}
value={currentTime}
onChange={(e) => {
setIsPlaying(false);
setCurrentTime(Number(e.target.value));
onTimeChange(Number(e.target.value));
}}
/>
<span>{new Date(currentTime).toLocaleString()}</span>
</div>
);
}
```
#### WebSocket Hook (useTrainUpdates.ts)
```typescript
function useTrainUpdates() {
const [trains, setTrains] = useState<Train[]>([]);
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