Files
ATLAS/backend/app/models/ubicacion.py
FlotillasGPS Developer 51d78bacf4 FlotillasGPS - Sistema completo de monitoreo de flotillas GPS
Sistema completo para monitoreo y gestion de flotas de vehiculos con:
- Backend FastAPI con PostgreSQL/TimescaleDB
- Frontend React con TypeScript y TailwindCSS
- App movil React Native con Expo
- Soporte para dispositivos GPS, Meshtastic y celulares
- Video streaming en vivo con MediaMTX
- Geocercas, alertas, viajes y reportes
- Autenticacion JWT y WebSockets en tiempo real

Documentacion completa y guias de usuario incluidas.
2026-01-21 08:18:00 +00:00

157 lines
5.1 KiB
Python

"""
Modelo de Ubicación para almacenar datos GPS.
Utiliza TimescaleDB hypertable para almacenamiento eficiente
de series temporales de ubicaciones.
"""
from datetime import datetime
from sqlalchemy import (
Boolean,
DateTime,
Float,
ForeignKey,
Index,
Integer,
String,
event,
)
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
class Ubicacion(Base):
"""
Modelo de ubicación GPS.
Esta tabla está diseñada para ser una hypertable de TimescaleDB,
optimizada para almacenar millones de registros de ubicación.
"""
__tablename__ = "ubicaciones"
# Clave primaria compuesta: tiempo + vehiculo_id
tiempo: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
primary_key=True,
nullable=False,
)
vehiculo_id: Mapped[int] = mapped_column(
ForeignKey("vehiculos.id", ondelete="CASCADE"),
primary_key=True,
nullable=False,
)
# Coordenadas
lat: Mapped[float] = mapped_column(Float, nullable=False)
lng: Mapped[float] = mapped_column(Float, nullable=False)
altitud: Mapped[float | None] = mapped_column(Float, nullable=True) # metros sobre nivel del mar
# Movimiento
velocidad: Mapped[float | None] = mapped_column(Float, nullable=True) # km/h
rumbo: Mapped[float | None] = mapped_column(Float, nullable=True) # grados (0-360)
# Precisión
precision: Mapped[float | None] = mapped_column(Float, nullable=True) # metros
hdop: Mapped[float | None] = mapped_column(Float, nullable=True) # Horizontal Dilution of Precision
# Información GPS
satelites: Mapped[int | None] = mapped_column(Integer, nullable=True)
fuente: Mapped[str] = mapped_column(
String(20),
default="gps",
nullable=False,
) # gps, network, fused, meshtastic
# Estado del dispositivo
bateria_dispositivo: Mapped[float | None] = mapped_column(Float, nullable=True) # porcentaje
bateria_vehiculo: Mapped[float | None] = mapped_column(Float, nullable=True) # voltaje
# Estado del vehículo
motor_encendido: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
odometro: Mapped[float | None] = mapped_column(Float, nullable=True) # km
# Sensores OBD (opcional)
rpm: Mapped[int | None] = mapped_column(Integer, nullable=True)
temperatura_motor: Mapped[float | None] = mapped_column(Float, nullable=True) # Celsius
nivel_combustible: Mapped[float | None] = mapped_column(Float, nullable=True) # porcentaje
# Índices para consultas frecuentes
__table_args__ = (
# Índice espacial aproximado para consultas por área
Index("idx_ubicaciones_coords", "lat", "lng"),
# Índice para consultas por vehículo en un rango de tiempo
Index("idx_ubicaciones_vehiculo_tiempo", "vehiculo_id", "tiempo"),
# Índice para encontrar paradas (velocidad 0)
Index("idx_ubicaciones_velocidad", "velocidad"),
# Configuración para TimescaleDB
{
"timescaledb_hypertable": {
"time_column_name": "tiempo",
"chunk_time_interval": "1 day",
}
},
)
def __repr__(self) -> str:
return f"<Ubicacion(vehiculo_id={self.vehiculo_id}, tiempo={self.tiempo}, lat={self.lat}, lng={self.lng})>"
def to_geojson(self) -> dict:
"""Convierte la ubicación a formato GeoJSON Point."""
return {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [self.lng, self.lat],
},
"properties": {
"vehiculo_id": self.vehiculo_id,
"tiempo": self.tiempo.isoformat(),
"velocidad": self.velocidad,
"rumbo": self.rumbo,
"motor_encendido": self.motor_encendido,
},
}
# Función para crear la hypertable en TimescaleDB
# Se ejecuta después de crear la tabla
def create_hypertable(target, connection, **kw):
"""Crea la hypertable de TimescaleDB después de crear la tabla."""
# Esta función se ejecutará solo si TimescaleDB está instalado
try:
connection.execute(
"""
SELECT create_hypertable(
'ubicaciones',
'tiempo',
if_not_exists => TRUE,
chunk_time_interval => INTERVAL '1 day'
);
"""
)
# Habilitar compresión después de 7 días
connection.execute(
"""
ALTER TABLE ubicaciones SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'vehiculo_id'
);
"""
)
# Política de compresión automática
connection.execute(
"""
SELECT add_compression_policy('ubicaciones', INTERVAL '7 days', if_not_exists => TRUE);
"""
)
except Exception:
# Si TimescaleDB no está instalado, continuar sin hypertable
pass
# Registrar evento para crear hypertable
event.listen(Ubicacion.__table__, "after_create", create_hypertable)